Pregunta

http://leepoint.net/notes-java/data/expressions/22compareobjects.html

Resulta que definir igual () no es trivial;De hecho, es moderadamente difícil hacerlo bien, especialmente en el caso de las subclases.El mejor tratamiento de los problemas es en el Core Java Vol 1 de Horstmann.

Si siempre se debe anular igualdad (), entonces, ¿cuál es un buen enfoque para no verse acorralado y tener que realizar una comparación de objetos?¿Cuáles son algunas buenas alternativas de "diseño"?

EDITAR:

No estoy seguro de que esto salga como esperaba.Tal vez la pregunta debería estar más en la línea de "¿Por qué querrías comparar dos objetos?" Según su respuesta a esa pregunta, ¿hay una solución alternativa a la comparación?No me refiero a una implementación diferente de iguales.Quiero decir, no usar la igualdad en absoluto.Creo que el punto clave es comenzar con esa pregunta: ¿por qué querrías comparar dos objetos?

¿Fue útil?

Solución

  

Si siempre deben ser anulados equals (),   a continuación, lo que es una buena aproximación para no   siendo arrinconado en tener que hacer   comparación de objetos?

Se equivoca. Debe reemplazar iguala lo menos posible.


Todo esta información proviene de Effective Java, segunda edición ( Josh Bloch ). El primer capítulo de esta edición todavía está disponible como un .

De Effective Java:

  

La forma más sencilla de evitar problemas es   no para anular el método equals, en   cuyo caso cada instancia de la clase   es igual solamente a sí mismo.

El problema de manera arbitraria anulando iguales / hashCode es herencia. Algunas implementaciones igual defensor de las pruebas de esta manera:

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

De hecho, el Eclipse (3.4) Java editor hace precisamente esto cuando se genera el método que utiliza la herramientas de código. De acuerdo con Bloch, esto es un error, ya que viola el Liskov principio de sustitución .

De Effective Java:

  

No hay manera de extender una   instanciable clase y añadir un valor   preservando al mismo tiempo los componentes iguales   contrato.

Dos maneras de minimizar los problemas de igualdad se describen en los clases e interfaces capítulo:

  1. composición Favor sobre la herencia
  2. Diseño y documentos para la herencia o bien prohibirla

Por lo que yo puedo ver, la única alternativa es poner a prueba la igualdad en una forma externa a la clase, y cómo eso se llevaría a cabo dependerá del diseño del tipo y el contexto que estaba tratando de usarlo en.

Por ejemplo, podría definir una interfaz que documenta la forma en que iba a ser comparado. En el siguiente código, las instancias de servicio pueden ser reemplazados en tiempo de ejecución con una nueva versión de la misma clase - en cuyo caso, teniendo diferentes cargadores de clases, es igual a comparaciones siempre volverían falsa, por lo primordial es igual a / hashCode sería redundante

.
public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

  

"¿Por qué quiere comparar dos objetos?"

El ejemplo obvio es probar si dos cadenas son el mismo (o dos archivos , o URI ). Por ejemplo, ¿qué pasaría si usted quiere construir un conjunto de archivos a analizar. Por definición, el conjunto contiene sólo elementos únicos. rel="nofollow Establecer tipo se basa en los iguales / métodos hashCode para hacer cumplir la singularidad de sus elementos.

Otros consejos

No creo que es verdad que siempre iguales deben ser anulados. La regla según entiendo es que primordial es igual sólo tiene sentido en los casos en que esté claro sobre cómo definir objetos semánticamente equivalentes. En ese caso, anular hashCode () también de modo que usted no tiene objetos que ha definido como equivalentes que regresan diferentes hashcodes.

Si no se puede definir la equivalencia significativa, no veo el beneficio.

¿Qué tal hacer las cosas bien?

Aquí está mi es igual a la plantilla, que es el conocimiento aplicado desde Java eficaz por Josh Bloch. Leer el libro para más detalles:

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

Mmhh

En algunos casos puede hacer que el objeto no se puede modificar (sólo lectura) y lo han creado a partir de un solo punto (un método de fábrica)

Si se necesitan dos objetos con los mismos datos de entrada (parámetros) de creación de la fábrica devolverá el mismo ref instancia y luego usando "==" sería suficiente.

Este enfoque es útil solo en determinadas circunstancias. Y la mayoría de las veces se vería un exceso.

Tome un vistazo a esta respuesta para saber cómo implementar tal cosa.

advirtiendo que es una gran cantidad de código

En breve ver cómo funciona la clase contenedora desde java 1,5

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

Es verdad mientras

new Integer( 2 ) == new Integer( 2 )  

es falso.

Se mantiene internamente la referencia y devolverlo si el valor de entrada es el mismo.

Como saben entero es de sólo lectura

Algo similar ocurre con la clase cadena de la que esa pregunta era acerca.

Tal vez estoy perdiendo el punto, pero la única razón para utilizar iguales en contraposición a la definición de su propio método con un nombre diferente es porque muchas de las Colecciones (y probablemente otras cosas en el JDK o como se llame en estos días) espera que el método equals para definir un resultado coherente. Pero más allá de eso, no puedo pensar en tres tipos de comparaciones que desee hacer en iguales:

  1. Los dos objetos realmente son la misma instancia. Esto no tiene sentido utilizar es igual porque se puede utilizar ==. También, y me corrija si me he olvidado de cómo funciona en Java, el valor predeterminado es igual método hace esto usando los códigos hash generados automáticamente.
  2. Los dos objetos tienen referencias a los mismos casos, pero no son la misma instancia. Esto es útil, uh, a veces ... sobre todo si se persistieron objetos y se refieren al mismo objeto en la DB. Usted tendría que definir su método es igual a hacer esto.
  3. Los dos objetos tienen referencias a objetos que son iguales en valor, aunque pueden o no pueden ser las mismas instancias (en otras palabras, se comparan los valores de todo el camino a través de la jerarquía).

¿Por qué quiere comparar dos objetos? Bueno, si son iguales, que se quiere hacer una cosa, y si no lo son, que le gustaría hacer otra cosa.

Dicho esto, depende del caso en cuestión.

La principal razón para anular iguales () en la mayoría de los casos es comprobar si hay duplicados dentro de ciertas colecciones. Por ejemplo, si desea utilizar un conjunto de contener un objeto que haya creado usted necesita para sobrescribir equals () y hashCode () dentro de su objeto. Lo mismo se aplica si usted desea utilizar el objeto personalizado como una clave en un mapa.

Esto es fundamental, ya que he visto que muchas personas cometen el error en la práctica de añadir sus objetos personalizados a Establece o Mapas sin anulando equals () y hashCode (). La razón de esto puede ser especialmente insidioso es el compilador no se quejará y se puede terminar con varios objetos que contienen los mismos datos, pero que tienen diferentes referencias en una colección que no permite duplicados.

Por ejemplo si tuviera un grano simple llamado NameBean con un único atributo de cadena 'nombre', se podía construir dos instancias de NameBean (por ejemplo nombre1 y nombre2), cada uno con el mismo 'nombre' valor de atributo (por ejemplo, "Alicia" ). A continuación, puede añadir tanto nombre1 y nombre2 a un conjunto y el conjunto sería de tamaño 2 en lugar del tamaño 1, que es lo que se pretende. Del mismo modo, si usted tiene un mapa como mapa con el fin de asignar el nombre de frijol a algún otro objeto, y que ha correlacionado primera nombre1 a la cadena "primera" y más tarde asignó nombre2 a la cadena "segundo" tendrá dos pares clave / valor en el mapa (por ejemplo, nombre1 -> "primero", nombre2 -> "segundo"). Así que cuando se hace un mapa de las operaciones de búsqueda que devolverá el valor asignado a la referencia exacta se pasa en, que es ya sea nombre1, nombre2, u otra referencia con el nombre de "Alicia" que devolverá nulo.

Este es un ejemplo concreto precedido por la salida de ejecutarlo:

Salida:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

Código:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

La igualdad es fundamental para la lógica (ver ley de identidad), y no hay mucha programación que puedas hacer sin ella.En cuanto a comparar instancias de clases que escribas, eso depende de ti.Si necesita poder encontrarlos en colecciones o usarlos como claves en Maps, necesitará controles de igualdad.

Si ha escrito más de unas pocas bibliotecas no triviales en Java, sabrá que es difícil lograr la igualdad, especialmente cuando las únicas herramientas en el cofre son equals y hashCode.La igualdad termina estando estrechamente ligada a las jerarquías de clases, lo que hace que el código sea frágil.Es más, no se proporciona ninguna verificación de tipos ya que estos métodos solo toman parámetros de tipo Objeto.

Hay una manera de hacer que la verificación de igualdad (y el hash) sea mucho menos propensa a errores y más segura para los tipos.En el Java funcional biblioteca, encontrarás Equal<A> (y un correspondiente Hash<A>) donde la igualdad se desacopla en una sola clase.Tiene métodos para componer. Equal instancias para sus clases a partir de instancias existentes, así como contenedores para colecciones, Iterables, HashMap, y Conjunto de hash, ese uso Equal<A> y Hash<A> en lugar de equals y hashCode.

Lo mejor de este enfoque es que nunca puedes olvidar escribir el método igual y hash cuando sea necesario.El sistema de tipos te ayudará a recordar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top