¿Por qué necesito anular los métodos iguales y hashCode en Java?
Recientemente leí este documento de Developer Works .
El documento trata sobre definir hashCode()
y equals()
de manera efectiva y correcta, sin embargo, no puedo entender por qué necesitamos anular estos dos métodos.
¿Cómo puedo tomar la decisión de implementar estos métodos de manera eficiente?
Joshua Bloch dice sobre Java efectivo
Debes anular hashCode() en cada clase que anule equals(). No hacerlo resultará en una violación del contrato general de Object.hashCode(), lo que impedirá que su clase funcione correctamente junto con todas las colecciones basadas en hash, incluidas HashMap, HashSet y Hashtable.
Intentemos entenderlo con un ejemplo de lo que sucedería si anulamos equals()
sin anular hashCode()
e intentamos usar un archivo Map
.
Digamos que tenemos una clase como esta y que dos objetos MyClass
son iguales si importantField
son iguales (con eclipse hashCode()
y equals()
generado por él)
public class MyClass {
private final String importantField;
private final String anotherField;
public MyClass(final String equalField, final String anotherField) {
this.importantField = equalField;
this.anotherField = anotherField;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((importantField == null) ? 0 : importantField.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MyClass other = (MyClass) obj;
if (importantField == null) {
if (other.importantField != null)
return false;
} else if (!importantField.equals(other.importantField))
return false;
return true;
}
}
Imagina que tienes esto
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
Anular sóloequals
Si solo equals
se anula, cuando llame myMap.put(first,someValue)
primero se aplicará el hash a algún depósito y cuando lo llame, myMap.put(second,someOtherValue)
se aplicará el hash a algún otro depósito (ya que tienen un diferente hashCode
). Entonces, aunque son iguales, como no hacen hash en el mismo depósito, el mapa no puede darse cuenta y ambos permanecen en el mapa.
Aunque no es necesario anular equals()
si anulamos hashCode()
, veamos qué pasaría en este caso particular donde sabemos que dos objetos MyClass
son iguales si importantField
son iguales pero no anulamos equals()
.
Anular sólohashCode
Si solo anula hashCode
, cuando llama, myMap.put(first,someValue)
toma primero, lo calcula hashCode
y lo almacena en un depósito determinado. Luego, cuando llame, myMap.put(second,someOtherValue)
debe reemplazar el primero por el segundo según la documentación del mapa porque son iguales (según los requisitos comerciales).
Pero el problema es que igual no se redefinió, por lo que cuando el mapa realiza un hash second
y recorre el depósito buscando si hay un objeto k
que second.equals(k)
sea verdadero, no encontrará ninguno como second.equals(first)
será false
.
Espero que haya sido claro
Las colecciones como HashMap
y HashSet
usan un valor de código hash de un objeto para determinar cómo debe almacenarse dentro de una colección, y el código hash se usa nuevamente para ubicar el objeto en su colección.
La recuperación de hash es un proceso de dos pasos:
- Encuentra el cubo correcto (usando
hashCode()
) - Busque en el depósito el elemento correcto (usando
equals()
)
Aquí hay un pequeño ejemplo de por qué debemos anular equals()
y hashcode()
.
Considere una Employee
clase que tiene dos campos: edad y nombre.
public class Employee {
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Employee))
return false;
Employee employee = (Employee) obj;
return employee.getAge() == this.getAge()
&& employee.getName() == this.getName();
}
// commented
/* @Override
public int hashCode() {
int result=17;
result=31*result+age;
result=31*result+(name!=null ? name.hashCode():0);
return result;
}
*/
}
Ahora cree una clase, inserte Employee
un objeto en HashSet
y pruebe si ese objeto está presente o no.
public class ClientTest {
public static void main(String[] args) {
Employee employee = new Employee("rajeev", 24);
Employee employee1 = new Employee("rajeev", 25);
Employee employee2 = new Employee("rajeev", 24);
HashSet<Employee> employees = new HashSet<Employee>();
employees.add(employee);
System.out.println(employees.contains(employee2));
System.out.println("employee.hashCode(): " + employee.hashCode()
+ " employee2.hashCode():" + employee2.hashCode());
}
}
Imprimirá lo siguiente:
false
employee.hashCode(): 321755204 employee2.hashCode():375890482
Ahora descomente hashcode()
el método, ejecute lo mismo y el resultado sería:
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
Ahora, ¿puedes ver por qué si dos objetos se consideran iguales, sus códigos hash también deben ser iguales? De lo contrario, nunca podrá encontrar el objeto, ya que el
método de código hash predeterminado en la clase Objeto prácticamente siempre genera un número único para cada objeto, incluso si el equals()
método se anula de tal manera que dos o más objetos se consideran iguales. . No importa cuán iguales sean los objetos si sus códigos hash no reflejan eso. Entonces, una vez más: si dos objetos son iguales, sus
códigos hash también deben ser iguales.
Debes anular hashCode() en cada clase que anule equals(). No hacerlo resultará en una violación del contrato general de Object.hashCode(), lo que impedirá que su clase funcione correctamente junto con todas las colecciones basadas en hash, incluidas HashMap, HashSet y Hashtable.
de Java efectivo , por Joshua Bloch
Al definirlo equals()
y hashCode()
hacerlo de manera consistente, puede mejorar la usabilidad de sus clases como claves en colecciones basadas en hash. Como explica el documento API para hashCode: "Este método se admite en beneficio de tablas hash como las proporcionadas por java.util.Hashtable
".
La mejor respuesta a su pregunta sobre cómo implementar estos métodos de manera eficiente es sugerirle que lea el Capítulo 3 de Java efectivo .