Java Challengers # 4: Comparación de objetos con equals () y hashCode ()
En previsión del lanzamiento de un nuevo hilo en el curso "Desarrollador Java", continuamos traduciendo una serie de artículos de Java Challengers, cuyas partes anteriores se pueden leer en los enlaces a continuación:
Vamos!
En este artículo, aprenderá cómo se relacionan los métodos equals()
y hashCode()
y cómo se usan al comparar objetos.

Sin usar equals()
y hashCode()
para comparar el estado de dos objetos, necesitamos escribir muchas comparaciones " if
" que comparen cada campo del objeto. Este enfoque hace que el código sea confuso y difícil de leer. Trabajando juntos, estos dos métodos ayudan a crear un código más flexible y consistente.
El código fuente del artículo está aquí .
Anulación de equals () y hashCode ()
La anulación de métodos es una técnica mediante la cual el comportamiento de una clase o interfaz principal se sobrescribe (redefine) en una subclase (consulte Desafíos de Java n. ° 3: Polimorfismo y herencia , ing. ). En Java, cada objeto tiene métodos equals()
y hashCode()
, y para que funcionen correctamente, deben ser anulados.
Para comprender cómo funciona la redefinición de equals()
y hashCode()
, examinemos su implementación en las clases base de Java. El siguiente es el método equals()
de la clase Object
. El método verifica si la instancia actual coincide con el objeto obj
pasado.
public boolean equals(Object obj) { return (this == obj); }
Ahora veamos el método hashCode()
en la clase Object
.
@HotSpotIntrinsicCandidate public native int hashCode();
Esto es nativo, un método que está escrito en otro lenguaje, como C, y devuelve un código numérico asociado con la dirección de memoria del objeto. (Si no está escribiendo código JDK, no es importante saber exactamente cómo funciona este método).
Nota del traductor: el valor asociado con la dirección no es completamente correcto ( gracias a vladimir_dolzhenko ). HotSpot JVM utiliza números pseudoaleatorios de forma predeterminada. La descripción de la implementación de hashCode () para HotSpot está aquí y aquí .
Si los métodos equals()
y hashCode()
no se hashCode()
los métodos de la clase Object
descrita anteriormente. En este caso, los métodos no cumplen el propósito real de equals()
y hashCode()
, que es verificar si los objetos tienen el mismo estado.
Por lo general, anular equals()
también anula hashCode()
.
Comparar objetos con iguales ()
El método equals()
se usa para comparar objetos. Para determinar si los objetos son idénticos o no, equals()
compara los valores del campo del objeto:
public class EqualsAndHashCodeExample { public static void main(String... args){ System.out.println(new Simpson("Homer", 35, 120) .equals(new Simpson("Homer",35,120))); System.out.println(new Simpson("Bart", 10, 120) .equals(new Simpson("El Barto", 10, 45))); System.out.println(new Simpson("Lisa", 54, 60) .equals(new Object())); } static class Simpson { private String name; private int age; private int weight; public Simpson(String name, int age, int weight) { this.name = name; this.age = age; this.weight = weight; } @Override public boolean equals(Object o) {
Veamos el método equals()
. La primera comparación compara la instancia actual de this
con el o
pasado. Si es el mismo objeto, equals()
devolverá true
.
La segunda comparación verifica si el objeto pasado es null
y de qué tipo es. Si el objeto transferido es de un tipo diferente, entonces los objetos no son iguales.
Finalmente, equals()
compara los campos de objetos. Si dos objetos tienen los mismos valores de campo, entonces los objetos son iguales.
Análisis de opciones para comparar objetos
Ahora veamos las opciones para comparar objetos en el método main()
. Primero, comparamos dos objetos de Simpson
:
System.out.println( new Simpson("Homer", 35, 120).equals( new Simpson("Homer", 35, 120)));
Los campos de estos objetos tienen los mismos valores, por lo que el resultado será true
.
Luego, nuevamente compare los dos objetos de Simpson
:
System.out.println( new Simpson("Bart", 10, 45).equals( new Simpson("El Barto", 10, 45)));
Los objetos aquí son similares, pero los significados de los nombres son diferentes: Bart y El Barto . Por lo tanto, el resultado será false
.
Finalmente, comparemos el objeto Simpson
y la instancia de la clase Object
:
System.out.println( new Simpson("Lisa", 54, 60).equals( new Object()));
En este caso, el resultado será false
, ya que los tipos de objetos son diferentes.
es igual a () versus ==
A primera vista, parece que el operador ==
y el método equals()
hacen lo mismo, pero, de hecho, funcionan de manera diferente. El operador ==
compara si dos enlaces apuntan al mismo objeto. Por ejemplo:
Simpson homer = new Simpson("Homer", 35, 120); Simpson homer2 = new Simpson("Homer", 35, 120); System.out.println(homer == homer2);
Creamos dos instancias diferentes de Simpson
usando el new
operador. Por lo tanto, las variables homer
y homer2
apuntarán a diferentes objetos en el montón . Por lo tanto, como resultado, nos hacemos false
.
En el siguiente ejemplo, usamos el método anulado equals()
:
System.out.println(homer.equals(homer2));
En este caso, se compararán los campos. Dado que ambos objetos Simpson
tienen los mismos valores de campo, el resultado será true
.
Identificación de objetos con hashCode ()
Para optimizar el rendimiento al comparar objetos, se utiliza el método hashCode()
. El método hashCode()
devuelve un identificador único para cada objeto, lo que simplifica la comparación de los estados del objeto.
Si el código hash de un objeto no coincide con el código hash de otro objeto, puede omitir el método equals()
: solo sabe que los dos objetos no coinciden. Por otro lado, si el código hash es el mismo, entonces debe ejecutar el método equals()
para determinar si los valores del campo coinciden.
Considere un ejemplo práctico con hashCode()
.
public class HashcodeConcept { public static void main(String... args) { Simpson homer = new Simpson(1, "Homer"); Simpson bart = new Simpson(2, "Homer"); boolean isHashcodeEquals = homer.hashCode() == bart.hashCode(); if (isHashcodeEquals) { System.out.println(" equals."); } else { System.out.println(" equals, .. " + " , , ."); } } static class Simpson { int id; String name; public Simpson(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Simpson simpson = (Simpson) o; return id == simpson.id && name.equals(simpson.name); } @Override public int hashCode() { return id; } } }
El método hashCode()
, que siempre devuelve el mismo valor, es válido pero no eficiente. En este caso, la comparación siempre devolverá true
, por lo que el método equals()
siempre se ejecutará. En este caso, no hay mejora del rendimiento.
Usando equals () y hashCode () con colecciones
Las clases que implementan la interfaz Set
(set) deben evitar que se agreguen elementos duplicados. A continuación hay algunas clases que implementan la interfaz Set
:
Solo se pueden agregar elementos únicos a Set
. Por lo tanto, si desea agregar un elemento, por ejemplo, a un HashSet
, primero debe usar los métodos equals()
y hashCode()
para asegurarse de que este elemento sea único. Si los métodos equals()
y hashCode()
no se han anulado, corre el riesgo de insertar valores duplicados.
Veamos la parte de implementación del método add()
en un HashSet
:
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e;
Antes de agregar un nuevo elemento, el HashSet
verifica si el elemento existe en esta colección. Si el objeto coincide, entonces el nuevo elemento no se insertará.
Los métodos equals()
y hashCode()
se usan no solo en Set
. Estos métodos también son necesarios para HashMap , Hashtable y LinkedHashMap . Como regla general, si ve una colección con el prefijo "Hash" , puede estar seguro de que para su correcto funcionamiento se requiere una anulación de los hashCode()
y equals()
.
Recomendaciones para usar equals () y hashCode ()
Ejecute el método equals()
solo para objetos con el mismo código hash. No ejecute equals()
si el código hash es diferente.
Tabla 1. Comparación de código hash
Si la comparación hashCode () ... | Que ... |
---|
devuelve true | ejecutar equals() |
devuelve false | no ejecute equals() |
Este principio se usa principalmente en colecciones Set
o Hash
por razones de rendimiento.
Reglas de comparación de objetos
Cuando la comparación hashCode()
devuelve false
, el método equals()
también debe devolver false
. Si el código hash es diferente, entonces los objetos definitivamente no son iguales.
Tabla 2. Comparación de objetos con hashCode ()
Cuando la comparación hashCode() devuelve ... | El método equals() debería devolver ... |
---|
true | true o false |
false | false |
Cuando el método equals()
devuelve true
, esto significa que los objetos son iguales en todos los valores y atributos . En este caso, la comparación del código hash también debería ser verdadera.
Tabla 3. Comparación de objetos con iguales ()
Cuando el método equals() regresa ... | El método hashCode() debería devolver ... |
---|
true | true |
false | true o false |
Resuelva el problema en equals () y hashCode ()
Es hora de probar su conocimiento de los métodos equals()
y hashCode()
. La tarea es encontrar el resultado de varios equals()
y el tamaño total de la colección Set
.
Para comenzar, estudie cuidadosamente el siguiente código:
public class EqualsHashCodeChallenge { public static void main(String... args) { System.out.println(new Simpson("Bart").equals(new Simpson("Bart"))); Simpson overriddenHomer = new Simpson("Homer") { public int hashCode() { return (43 + 777) + 1; } }; System.out.println(new Simpson("Homer").equals(overriddenHomer)); Set set = new HashSet(Set.of(new Simpson("Homer"), new Simpson("Marge"))); set.add(new Simpson("Homer")); set.add(overriddenHomer); System.out.println(set.size()); } static class Simpson { String name; Simpson(String name) { this.name = name; } @Override public boolean equals(Object obj) { Simpson otherSimpson = (Simpson) obj; return this.name.equals(otherSimpson.name) && this.hashCode() == otherSimpson.hashCode(); } @Override public int hashCode() { return (43 + 777); } } }
Primero, analice el código, piense cuál será el resultado. Y solo entonces ejecuta el código. El objetivo es mejorar sus habilidades de análisis de código y aprender conceptos básicos de Java para que pueda mejorar su código.
¿Cuál será el resultado?
A) true true 4 B) true false 3 C) true false 2 D) false true 3
Que paso Comprender equals () y hashCode ()
En la primera comparación, el resultado de equals()
es true
, ya que los estados de los objetos son los mismos, y el método hashCode()
devuelve el mismo valor para ambos objetos.
En la segunda comparación, el método hashCode()
se anuló para la variable hashCode()
. Para ambos objetos Simpson
, el nombre es "Homer" , pero para hashCode()
overriddenHomer
el método hashCode()
devuelve un valor diferente. En este caso, el resultado del método equals()
será false
, ya que contiene una comparación con el código hash.
Debes haberte dado cuenta de que habrá tres objetos Simpson
en la colección. Vamos a hacerlo.
El primer objeto en el conjunto se insertará como de costumbre:
new Simpson("Homer");
El siguiente objeto también se insertará de la manera habitual, ya que contiene un valor diferente del objeto anterior:
new Simpson("Marge");
Finalmente, el siguiente objeto de Simpson
tiene el mismo valor de nombre que el primer objeto. En este caso, el objeto no se insertará:
set.add(new Simpson("Homer"));
Como sabemos, el objeto overridenHomer
usa un valor hash diferente, a diferencia de una instancia de Simpson("Homer")
normal Simpson("Homer")
. Por este motivo, este elemento se insertará en la colección:
set.add(overriddenHomer);
La respuesta
La respuesta correcta es B. La conclusión será:
true false 3
Errores comunes con equals () y hashCode ()
- Falta de anulación de
hashCode()
junto con anulación de equals()
o viceversa. - Falta de anulación de
equals()
y hashCode()
cuando se usan colecciones hash como HashSet
. - Devolver un valor constante en el método
hashCode()
lugar de devolver un código único para cada objeto. - Uso equivalente de
==
y equals()
. El operador ==
compara referencias de objetos, mientras que el método equals()
compara valores de objetos.
Lo que debe recordar sobre equals () y hashCode ()
- Se recomienda que siempre anule los métodos
equals()
y hashCode()
en sus POJO ( ruso , inglés ) - Use un algoritmo eficiente para crear un código hash único.
- Al anular el método
equals()
, siempre anule el método hashCode()
. - El método
equals()
debe comparar el estado completo de los objetos (valores de los campos). - El método
hashCode()
puede ser un identificador (ID) POJO. - Si el resultado de comparar el código hash de los dos objetos es
false
, entonces el método equals()
también debe ser false
. - Si
equals()
y hashCode()
no se redefinen al usar colecciones hash, entonces la colección tendrá elementos duplicados.
Aprenda más sobre Java
Tradicionalmente, espero sus comentarios y los invito a una lección abierta , que será impartida por nuestro maestro Sergei Petrelevich el 18 de marzo.