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.
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