2011-01-20 12 views
19

¿Hay alguna situación en la que tenga sentido para una clase implementar sus métodos equals() y hashCode() usando un conjunto diferente de los campos de clase?Java equal() y hashCode() basados ​​en campos diferentes?

Lo estoy preguntando porque estoy desconcertado por el generador Netbeans equals() y hashCode(), donde se le pide que elija los campos para incluir en cada método por separado. Siempre termino seleccionando los mismos campos para ambos métodos, pero ¿hay alguna situación en la que esta no sea la opción correcta?

+1

Con la implicación de que el generador de código de Netbeans es incorrecto para dar la opción si nunca hay una buena razón para elegir diferentes campos. – Raedwald

Respuesta

22

Bueno, equals() debe utilizar todos los campos utilizados por hashCode(), ya que de lo contrario podría obtener diferentes códigos hash de objetos iguales. Sin embargo, lo contrario no es cierto: usted podría elegir no tener en cuenta un campo en particular al elegir el código hash. De esta forma, podría terminar con el mismo código hash para dos objetos desiguales que solo difieren en ese campo "no utilizado" (en oposición a las colisiones naturales). Solo querría eso en una situación en la que sabía que las colisiones serían poco probables, pero donde se estaría procesando un lote. Imagino que es extremadamente raro :)

Otro caso sería cuando tenía algún tipo de comparación de igualdad personalizada, como comparaciones de cadenas insensibles a mayúsculas y minúsculas, donde es difícil o costoso generar un código hash para el campo. De nuevo, esto daría lugar a una mayor probabilidad de colisión, pero sería válido.

+1

"podría elegir no tener en cuenta [algunos campos]", por ejemplo, si esos campos simplemente tienen un valor almacenado en caché de otros campos. – Raedwald

+0

@Raedwald: ¿Pero por qué incluiría esos campos para la igualdad en ese caso? Quizás he entendido mal tu sugerencia ... pero tienes razón de que los campos precalculados pueden ser relevantes. –

+0

Usted ** excluiría ** los campos que tenían valores almacenados en caché. – Raedwald

2

Generalmente, debe usar los mismos campos. Desde el equals() documentación:

Tenga en cuenta que en general es necesario reemplazar el método hashCode cada vez que se anula este método, a fin de mantener el contrato general para el método hashCode, que establece que los objetos iguales deben tener códigos hash iguales .

De la documentación hashCode():

Si dos objetos son iguales de acuerdo con el método equals (Object), a continuación, una llamada al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero.

Tenga en cuenta que a la inversa no es cierto - que puede tener dos objetos con el mismo código hash que no son iguales (Así es como algunas estructuras de datos se resuelven las colisiones)

Así, teóricamente, es posible utilizar un subconjunto de los campos del método equals(..) para el método hashCode(), pero no puedo pensar si es una razón práctica para hacerlo.

+1

Esto está mal. No es necesario que dos objetos con el mismo código hash sean iguales, por lo que es suficiente usar un subconjunto de campos usados ​​para calcular un código hash. Ver la respuesta de Jon Skeet. – sfussenegger

+1

@sfussenegger gracias, corrigió mi declaración. – Bozho

+2

las únicas razones prácticas que se me ocurren son la pereza - vamos, es una mecanografía a lo grande;) y el rendimiento, por ej. si una colección contribuye a la igualdad. Además, podría haber uno o dos campos que están cerca de ser únicos. por cierto, el voto a favor no fue mío;) – sfussenegger

1

No creo que exista. I blogged about this topic previously - Creo que es un error de UI en NetBeans el hecho de que te permiten elegirlos independientemente el uno del otro. Desde mi entrada en el blog:

Este post from bytes.com hace un buen trabajo de explicar esto:

Sustitución del método hashCode.

El contrato para el método equals debería tener otra línea que indique que debe proceder a anular el método hashCode después de anular el método equals. El método hashCode es compatible con el beneficio de las colecciones basadas en hash.

El contrato

de nuevo de las especificaciones:

Siempre que se invoca en el mismo objeto más de una vez durante una ejecución de una aplicación, el método hashCode debe volver consistentemente el mismo número entero, siempre hay información utilizado en las comparaciones iguales en el objeto se modifica. Este entero no necesita ser consistente desde una ejecución de una aplicación hasta otra ejecución de la misma aplicación. Si dos objetos son iguales de acuerdo con el método equals (Object), al llamar al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero. No es necesario que si dos objetos son desiguales según el método equals, al llamar al método hashCode en cada uno de los dos objetos debe producir resultados enteros distintos. Sin embargo, el programador debe tener en cuenta que la producción de resultados enteros distintos para objetos desiguales puede mejorar el rendimiento de las tablas hash. Por lo tanto, los objetos iguales deben tener hashCodes iguales. Una manera fácil de garantizar que esta condición siempre se cumpla es usar los mismos atributos que se usan para determinar la igualdad al determinar el código hash. Ahora debería ver por qué es importante anular hashCode cada vez que sobrescribe equals.

Esa frase del último párrafo resume todo: “Una manera fácil de asegurarse de que esta condición se cumple siempre es utilizar los mismos atributos utilizados en la determinación de la igualdad en la determinación del código hash”.

2

Jon Skeet hizo un buen trabajo respondiendo esta pregunta (como siempre hace). Sin embargo, me gustaría añadir que se trata de una aplicación válida para cualquier aplicación de iguales

public int hashCode() { 
    return 42; 
} 

Naturalmente, el rendimiento de las estructuras de datos arbitrarios degradará de forma espectacular. Sin embargo, es mejor matar el rendimiento que romperlos. Entonces, si alguna vez decides anular iguales, pero no ves la necesidad de proporcionar una implementación de código hash, ese es el camino del perezoso.

1

Como seguimiento de la respuesta de Jon Skeet, recientemente me encontré con un caso en el que necesitaba implementar el método hashCode con solo un subconjunto de los campos utilizados en el método equals. El escenario (simplificado) es el siguiente:

Tengo dos clases A y B que contienen una referencia a la otra, además de tener definida una clave String. Usando el código hash automática e iguala generador en Eclipse (que, a diferencia de Netbeans, sólo da la opción de utilizar los mismos campos en ambos métodos) termino con las siguientes clases:

public class A { 

    public B b; 
    public String bKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((b == null) ? 0 : b.hashCode()); 
     result = prime * result + ((bKey == null) ? 0 : bKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof A)) 
      return false; 
     A other = (A) obj; 
     if (b == null) { 
      if (other.b != null) 
       return false; 
     } else if (!b.equals(other.b)) 
      return false; 
     if (bKey == null) { 
      if (other.bKey != null) 
       return false; 
     } else if (!bKey.equals(other.bKey)) 
      return false; 
     return true; 
    } 
} 

public class B { 

    public A a; 
    public String aKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((a == null) ? 0 : a.hashCode()); 
     result = prime * result + ((aKey == null) ? 0 : aKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof B)) 
      return false; 
     B other = (B) obj; 
     if (a == null) { 
      if (other.a != null) 
       return false; 
     } else if (!a.equals(other.a)) 
      return false; 
     if (aKey == null) { 
      if (other.aKey != null) 
       return false; 
     } else if (!aKey.equals(other.aKey)) 
      return false; 
     return true; 
    } 
} 

El problema surgió cuando traté añadir la clase a a un HashSet de la siguiente manera:

public static void main(String[] args) { 

     A a = new A(); 
     B b = new B(); 
     a.b = b; 
     b.a = a; 

     Set<A> aSet = new HashSet<A>(); 
     aSet.add(a); 
    } 

Esto terminará en un StackOverflowError desde la adición de a a aSet resultará en a 'método hashCode s de ser llamado, lo que resultará en b' s hashCode siendo llamado, que será esult en a 's hashCode llamándose, etc., etc., etc.La única forma de evitar esto es o bien eliminar la referencia a A de B 's hashCode y equals o sólo incluir la String bKey en B' método hashCode s. Como quería que el método B.equals incluyera la referencia A para verificar la igualdad, lo único que podía hacer era hacer que B.hashCode utilizara solo un subconjunto de los campos que se usaron en B.equals, es decir, solo use B.bKey en B.hashCode. No pude ver otra forma de evitar esto.

Posiblemente mi diseño es defectuoso y doy la bienvenida a alguien para señalarlo, pero esta es esencialmente la forma en que los objetos de mi dominio están estructurados en mi programa real.

Cuestiones relacionadas