¿Qué problemas / dificultades deben considerarse al anular equals
y hashCode
?
¿Qué problemas / dificultades deben considerarse al anular equals
y hashCode
?
Respuestas:
equals()
( javadoc ) debe definir una relación de equivalencia (debe ser reflexiva , simétrica y transitiva ). Además, debe ser coherente (si los objetos no se modifican, debe seguir devolviendo el mismo valor). Además, o.equals(null)
siempre debe devolver falso.
hashCode()
( javadoc ) también debe ser coherente (si el objeto no se modifica en términos de equals()
, debe seguir devolviendo el mismo valor).
La relación entre los dos métodos es:
Cada vez
a.equals(b)
, entoncesa.hashCode()
debe ser igual queb.hashCode()
.
Si anula uno, debe anular el otro.
Use el mismo conjunto de campos que usa para calcular equals()
para calcular hashCode()
.
Utilice las excelentes clases auxiliares EqualsBuilder y HashCodeBuilder de la biblioteca Lang de Apache Commons . Un ejemplo:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
Cuando utilice una colección o mapa basado en hash , como HashSet , LinkedHashSet , HashMap , Hashtable o WeakHashMap , asegúrese de que el hashCode () de los objetos clave que coloca en la colección nunca cambie mientras el objeto está en la colección. La forma a prueba de balas para garantizar esto es hacer que sus claves sean inmutables, lo que también tiene otros beneficios .
instanceof
devuelve falso si su primer operando es nulo (Java efectivo nuevamente).
Hay algunos problemas que vale la pena tener en cuenta si se trata de clases que persisten utilizando un Mapeador de relaciones de objetos (ORM) como Hibernate, ¡si no creía que esto ya era excesivamente complicado!
Los objetos con carga lenta son subclases
Si sus objetos persisten usando un ORM, en muchos casos estará tratando con proxys dinámicos para evitar cargar objetos demasiado pronto desde el almacén de datos. Estos proxies se implementan como subclases de su propia clase. Esto significa que this.getClass() == o.getClass()
volverá false
. Por ejemplo:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
Si se trata de un ORM, usar o instanceof Person
es lo único que se comportará correctamente.
Los objetos cargados perezosos tienen campos nulos
Los ORM generalmente usan los captadores para forzar la carga de objetos con carga lenta. Esto significa que person.name
será null
si person
está cargado de forma diferida, incluso si person.getName()
fuerza la carga y devuelve "John Doe". En mi experiencia, esto surge más a menudo en hashCode()
y equals()
.
Si se trata de un ORM, asegúrese de usar siempre getters y nunca coloque referencias en hashCode()
y equals()
.
Guardar un objeto cambiará su estado
Los objetos persistentes a menudo usan un id
campo para mantener la clave del objeto. Este campo se actualizará automáticamente cuando se guarde un objeto por primera vez. No use un campo de identificación en hashCode()
. Pero puedes usarlo equals()
.
Un patrón que uso a menudo es
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Pero: no puedes incluir getId()
en hashCode()
. Si lo hace, cuando un objeto persiste, sus hashCode
cambios. Si el objeto está en a HashSet
, "nunca" lo encontrará de nuevo.
En mi Person
ejemplo, probablemente usaría getName()
for hashCode
y getId()
plus getName()
(solo para paranoia) para equals()
. Está bien si hay algún riesgo de "colisiones" hashCode()
, pero nunca está bien equals()
.
hashCode()
debe usar el subconjunto de propiedades que no cambia equals()
Saving an object will change it's state
! hashCode
debe volver int
, entonces, ¿cómo va a usar getName()
? ¿Puede dar un ejemplo para suhashCode
Una aclaración sobre el obj.getClass() != getClass()
.
Esta declaración es el resultado de equals()
ser una herencia hostil. Los JLS (especificación del lenguaje Java) especifica que si A.equals(B) == true
entonces B.equals(A)
también hay que volver true
. Si omite esa declaración, las clases heredadas que anulan equals()
(y cambian su comportamiento) romperán esta especificación.
Considere el siguiente ejemplo de lo que sucede cuando se omite la declaración:
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
Haciendo new A(1).equals(new A(1))
también, el new B(1,1).equals(new B(1,1))
resultado es verdadero, como debería ser.
Todo esto se ve muy bien, pero mira lo que sucede si intentamos usar ambas clases:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Obviamente, esto está mal.
Si desea garantizar la condición simétrica. a = b si b = a y el principio de sustitución de Liskov llama super.equals(other)
no solo en el caso de la B
instancia, sino que comprueba después, por A
ejemplo:
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
Lo que dará salida:
a.equals(b) == true;
b.equals(a) == true;
Donde, si a
no es una referencia de B
, entonces podría ser un ser un referente de clase A
(porque extenderla), en este caso se llama super.equals()
demasiado .
ThingWithOptionSetA
puede ser igual a a, Thing
siempre que todas las opciones adicionales tengan valores predeterminados, y del mismo modo para a ThingWithOptionSetB
, entonces debería ser posible ThingWithOptionSetA
comparar a igual a a ThingWithOptionSetB
solo si todas las propiedades no básicas de ambos objetos coinciden con sus valores predeterminados, pero No veo cómo se prueba para eso.
B b2 = new B(1,99)
, entonces b.equals(a) == true
y a.equals(b2) == true
pero b.equals(b2) == false
.
Para una implementación amigable con la herencia, consulte la solución de Tal Cohen, ¿Cómo implemento correctamente el método equals ()?
Resumen:
En su libro Guía efectiva del lenguaje de programación Java (Addison-Wesley, 2001), Joshua Bloch afirma que "simplemente no hay forma de extender una clase instanciable y agregar un aspecto mientras se preserva el contrato igual". Tal no está de acuerdo.
Su solución es implementar equals () llamando a otro no simétrico blindlyEquals () en ambos sentidos. blindlyEquals () se reemplaza por subclases, se hereda equals () y nunca se reemplaza.
Ejemplo:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Tenga en cuenta que equals () debe funcionar en todas las jerarquías de herencia si se debe cumplir el Principio de sustitución de Liskov .
if (this.getClass() != o.getClass()) return false
, pero flexible, ya que solo devuelve falso si las clases derivadas se molestan en modificar iguales. ¿Está bien?
Todavía sorprendido de que ninguno recomendó la biblioteca de guayaba para esto.
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
this
en this.getDate()
medios nada (que no sea el desorden)
if (!(otherObject instanceof DateAndPattern)) {
. De acuerdo con Hernan y Steve Kuo (aunque ese es un asunto de preferencia personal), pero de todos modos +1.
Hay dos métodos en superclase como java.lang.Object. Necesitamos anularlos al objeto personalizado.
public boolean equals(Object obj)
public int hashCode()
Los objetos iguales deben producir el mismo código hash siempre que sean iguales, sin embargo, los objetos desiguales no necesitan producir códigos hash distintos.
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
Si desea obtener más, consulte este enlace como http://www.javaranch.com/journal/2002/10/equalhash.html
Este es otro ejemplo, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
¡Que te diviertas! @. @
Hay un par de formas de verificar la igualdad de clase antes de verificar la igualdad de los miembros, y creo que ambas son útiles en las circunstancias correctas.
instanceof
operador.this.getClass().equals(that.getClass())
.Uso # 1 en una final
implementación igual, o cuando implemento una interfaz que prescribe un algoritmo para iguales (como las java.util
interfaces de colección, la forma correcta de verificar con (obj instanceof Set)
o con cualquier interfaz que esté implementando). En general, es una mala elección cuando se pueden anular iguales porque eso rompe la propiedad de simetría.
La opción # 2 permite que la clase se extienda de manera segura sin anular iguales o romper la simetría.
Si su clase también lo es Comparable
, los métodos equals
y también compareTo
deben ser consistentes. Aquí hay una plantilla para el método igual en una Comparable
clase:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
final
, y el compareTo()
método se anuló para invertir el orden de clasificación, las instancias de la subclase y la superclase no deberían considerarse iguales. Cuando estos objetos se usaron juntos en un árbol, las claves que eran "iguales" según una instanceof
implementación podrían no ser encontrables.
Para los iguales, busque Secretos de los iguales de Angelika Langer . Yo la amo mucho. También es una gran pregunta frecuente sobre genéricos en Java . Vea sus otros artículos aquí (desplácese hacia abajo hasta "Core Java"), donde también continúa con la Parte 2 y la "comparación de tipos mixtos". ¡Diviértete leyéndolos!
El método equals () se utiliza para determinar la igualdad de dos objetos.
ya que el valor int de 10 siempre es igual a 10. Pero este método equals () trata sobre la igualdad de dos objetos. Cuando decimos objeto, tendrá propiedades. Para decidir sobre la igualdad, se consideran esas propiedades. No es necesario que se tengan en cuenta todas las propiedades para determinar la igualdad y, con respecto a la definición de clase y el contexto, se puede decidir. Entonces el método equals () puede ser anulado.
siempre debemos anular el método hashCode () siempre que anulemos el método equals (). Si no, ¿qué pasará? Si usamos tablas hash en nuestra aplicación, no se comportará como se esperaba. Como el hashCode se usa para determinar la igualdad de los valores almacenados, no devolverá el valor correspondiente correcto para una clave.
La implementación predeterminada dada es el método hashCode () en la clase Object utiliza la dirección interna del objeto y lo convierte en entero y lo devuelve.
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
Ejemplo de salida de código:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Un problema que he encontrado es donde dos objetos contienen referencias entre sí (un ejemplo es una relación padre / hijo con un método de conveniencia en el padre para obtener todos los hijos).
Este tipo de cosas son bastante comunes cuando se hacen asignaciones de Hibernate, por ejemplo.
Si incluye ambos extremos de la relación en su código hash o pruebas iguales, es posible entrar en un bucle recursivo que termina en una excepción StackOverflowException.
La solución más simple es no incluir la colección getChildren en los métodos.
equals()
. Si un científico loco creara un duplicado de mí, seríamos equivalentes. Pero no tendríamos el mismo padre.