¿Por qué anular los métodos iguales y de código hash en Java?
Fuente:
Medio Este artículo se centra en dos métodos estrechamente relacionados: equals() y hashcode() . Aprenderá cómo interactúan entre sí y cómo anularlos correctamente.
¿Por qué anulamos el método equals()?
En Java, no podemos sobrecargar el comportamiento de operadores como
== ,
+= ,
-+ . Trabajan según un proceso determinado. Por ejemplo, considere la operación del operador
== .
¿Cómo funciona el operador ==?
Comprueba si las dos referencias que se comparan apuntan a la misma instancia en la memoria. El operador
== solo se evaluará como verdadero si las dos referencias representan la misma instancia en la memoria. Echemos un vistazo al código de muestra:
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
Digamos que en su programa ha creado dos objetos
Persona en diferentes lugares y desea compararlos.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
Desde una perspectiva empresarial, los dos parecen iguales, ¿verdad? Pero para la JVM no son lo mismo. Dado que ambas se crean utilizando la
nueva palabra clave , estas instancias se ubican en diferentes segmentos de memoria. Por lo tanto, el operador
== devolverá
false . Pero si no podemos anular el operador
== , ¿cómo le decimos a la JVM que queremos que estos dos objetos sean tratados de la misma manera? Aquí es donde entra en juego
el método .equals() . Puedes anular
equals() para comprobar si algunos objetos tienen los mismos valores para ciertos campos para poder considerarlos iguales. Puede elegir qué campos comparar. Si decimos que dos objetos
Persona serán iguales sólo si tienen la misma edad y el mismo nombre, entonces el IDE generará algo como esto para crear automáticamente
iguales() :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
Volvamos a nuestro ejemplo anterior.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
Sí, no podemos sobrecargar el operador
== para comparar objetos de la forma que queramos, pero Java nos ofrece otra forma: el método
equals() , que podemos anular como queramos.
Tenga en cuenta que si no proporcionamos nuestra versión personalizada de .equals() (también conocida como anulación) en nuestra clase, entonces el .equals() predefinido de la clase Objeto y el operador == se comportarán igual. El método
equals() predeterminado , heredado de
Object , verificará si ambas instancias que se comparan son iguales en la memoria.
¿Por qué anulamos el método hashCode()?
Algunas estructuras de datos en Java, como
HashSet y
HashMap , almacenan sus elementos en función de una función hash que se aplica a esos elementos. La función hash es
hashCode() . Si tenemos la opción de anular
el método .equals() , entonces también deberíamos tener la opción de anular
el método hashCode() . Hay una razón para esto. Después de todo, la implementación predeterminada
de hashCode() , heredada de
Object , ¡considera que todos los objetos en la memoria son únicos! Pero volvamos a estas estructuras de datos hash. Existe una regla para estas estructuras de datos.
Un HashSet no puede contener valores duplicados y un HashMap no puede contener claves duplicadas. Un HashSet se implementa utilizando
un HashMap de tal manera que cada valor
de HashSet se almacena como una clave en
el HashMap . ¿Cómo funciona
HashMap ?
HashMap es una matriz nativa con múltiples segmentos. Cada segmento tiene una lista vinculada (
linkedList ). Esta lista vinculada almacena nuestras claves.
HashMap encuentra la lista vinculada correcta para cada clave usando el método
hashCode() y luego recorre en iteración todos los elementos de esa
lista vinculada y aplica el método
equals() a cada uno de esos elementos para verificar si ese elemento está contenido allí.
No se permiten claves duplicadas. Cuando ponemos algo dentro
de un HashMap , la clave se almacena en una de estas listas vinculadas. En qué lista vinculada se almacenará esta clave se muestra mediante el resultado del método
hashCode() para esa clave. Es decir, si
key1.hashCode() da como resultado 4, entonces esa
clave1 se almacenará en el cuarto segmento de la matriz en
LinkedList existente allí . De forma predeterminada, el método
hashCode() devuelve resultados diferentes para cada instancia. Si tenemos un valor predeterminado
igual () que se comporta como
== , tratando todas las instancias en la memoria como objetos diferentes, entonces no habrá ningún problema. Como recordará, en nuestro ejemplo anterior dijimos que queremos que las instancias
de Persona se consideren iguales si sus edades y nombres son los mismos.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
Ahora creemos un mapa para almacenar estas instancias como claves con una cadena específica como par de valores.
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
En la clase
Persona , no hemos anulado el método
hashCode , pero tenemos un método
igual anulado . Dado que el
hashCode predeterminado proporciona resultados diferentes para diferentes instancias Java
de person1.hashCode() y
person2.hashCode() , existen muchas posibilidades de obtener resultados diferentes. Nuestro mapa puede terminar con diferentes
personas en diferentes listas enlazadas.
Esto va en contra de la lógica
de HashMap .
Después de todo, ¡ un HashMap no puede tener varias claves idénticas! El punto es que el
hashCode() predeterminado heredado de la clase
Object no es suficiente. Incluso después de anular el método
equals() de la clase
Persona . Es por eso que tenemos que anular el método
hashCode() después de anular el método
igual . Ahora arreglemos esto. Necesitamos anular nuestro método
hashCode() para que tenga en cuenta los mismos campos que
equals() , es decir,
edad y
nombre .
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
int prime = 31;
return prime*Objects.hash(name, age);
}
En el método
hashCode() usamos un valor simple (puedes usar cualquier otro valor). Sin embargo, se sugiere utilizar números primos para crear menos problemas.
Intentemos almacenar estas claves en nuestro HashMap nuevamente :
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode() y
person2.hashCode() serán iguales. Digamos que son 0.
El HashMap irá al segmento 0 y en él
el LinkedList guardará
a persona1 como clave con el valor “1”. En el segundo caso, cuando
HashMap vuelva al depósito 0 para almacenar la clave
persona2 con el valor “2”, verá que ya existe allí otra clave igual. De esta forma sobrescribirá la clave anterior. Y solo la clave
person2 existirá en nuestro
HashMap . ¡Así aprendimos cómo funciona la regla
HashMap , que establece que no se pueden usar varias claves idénticas!
Sin embargo, tenga en cuenta que las instancias desiguales pueden tener el mismo código hash y las instancias iguales deben devolver el mismo código hash.
GO TO FULL VERSION