"Ahora te hablaré de algunos métodos que son igual de útiles:  equals(Object o) & hashCode() ".

"Probablemente ya hayas recordado que, en Java, cuando se comparan variables de referencia, no se comparan los objetos en sí, sino las referencias a los objetos".

Código Explicación
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i no es igual a j
Las variables apuntan a diferentes objetos.
Aunque los objetos contienen los mismos datos.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
yo es igual a j. Las variables contienen una referencia al mismo objeto.

"Sí, lo recuerdo".

Los  iguales .

"El método de igualdad es la solución estándar aquí. El propósito del método de igualdad es determinar si los objetos son internamente idénticos comparando lo que está almacenado dentro de ellos".

"¿Y cómo hace eso?"

"Todo es muy similar al método toString()".

La clase Object tiene su propia implementación del método equals, que simplemente compara las referencias:

public boolean equals(Object obj)
{
return (this == obj);
}

"Genial... volvamos a eso otra vez, ¿verdad?"

"¡Mantén la barbilla en alto! En realidad es muy complicado".

"Este método se creó para permitir a los desarrolladores sobrescribirlo en sus propias clases. Después de todo, solo el desarrollador de una clase sabe qué datos son relevantes y cuáles no cuando se comparan".

"¿Puede dar un ejemplo?"

"Claro. Supongamos que tenemos una clase que representa fracciones matemáticas. Se vería así:"

Ejemplo:
class Fraction
{
private int numerator;
private int denominator;
Fraction(int numerator, int denominator)
{
this.numerator  = numerator;
this.denominator = denominator;
}public boolean equals(Object obj)
{
if (obj==null)
return false;

if (obj.getClass() != this.getClass() )
return false;

Fraction other = (Fraction) obj;
return this.numerator* other.denominator == this.denominator * other.numerator;
}
}
Llamada de método de ejemplo:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
La llamada al método devolverá verdadero.
La fracción 2/3 es igual a la fracción 4/6

"Ahora, analicemos este ejemplo".

"Anulamos el método de igualdad , por lo que los objetos Fraction tendrán su propia implementación.

"Hay varias comprobaciones en el método:"

" 1)  Si el objeto pasado para la comparación es nulo , entonces los objetos no son iguales. Si puede llamar al método equals en un objeto, definitivamente no es nulo ".

" 2)  Una comparación de clases. Si los objetos son instancias de diferentes clases, entonces no intentaremos compararlos. En su lugar, usaremos inmediatamente return false para indicar que estos son objetos diferentes".

" 3)  Todos recuerdan del segundo grado que 2/3 es igual a 4/6. Pero, ¿cómo se comprueba eso?"

2/3 == 4/6
Multiplicamos ambos lados por ambos divisores (6 y 3), y obtenemos:
6 * 2 == 4 * 3
12 == 12
Regla general:
Si
a / b == c / d
Entonces
a * d == c * b

"En consecuencia, en la tercera parte del método de igualdad , convertimos el objeto pasado en una fracción y comparamos las fracciones".

"Entendido. Si simplemente comparamos el numerador con el numerador y el denominador con el denominador, entonces 2/3 no es igual a 4/6".

"Ahora entiendo lo que quisiste decir cuando dijiste que solo el desarrollador de una clase sabe cómo compararla correctamente".

"Sí, pero eso es solo la mitad de la historia.  Hay otro método: hashCode() " .

"Todo sobre el método equals tiene sentido ahora, pero ¿por qué necesitamos  hashCode ()? "

"El método hashCode es necesario para comparaciones rápidas".

"El método equals tiene una gran desventaja: funciona demasiado lento. Supongamos que tiene un conjunto de millones de elementos y necesita verificar si contiene un objeto específico. ¿Cómo lo hace?"

"Podría recorrer todos los elementos usando un bucle y comparar el objeto con cada objeto del conjunto. Hasta que encuentre una coincidencia".

"¿Y si no está allí? ¿Realizaríamos un millón de comparaciones solo para descubrir que el objeto no está allí? ¿No parece eso mucho?"

"Sí, incluso reconozco que son demasiadas comparaciones. ¿Hay otra forma?"

"Sí, puede usar hashCode () para esto.

El método hashCode () devuelve un número específico para cada objeto. El desarrollador de una clase decide qué número se devuelve, tal como lo hace con el método de igualdad.

"Veamos un ejemplo:"

"Imagine que tiene un millón de números de 10 dígitos. Luego, podría hacer que el código hash de cada número sea el resto después de dividir el número por 100".

Aquí hay un ejemplo:

Número Nuestro código hash
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Sí, eso tiene sentido. ¿Y qué hacemos con este hashCode?"

"En lugar de comparar los números, comparamos sus códigos hash . Es más rápido de esa manera".

"Y llamamos iguales solo si sus hashCodes son iguales".

"Sí, eso es más rápido. Pero todavía tenemos que hacer un millón de comparaciones. Solo estamos comparando números más pequeños, y todavía tenemos que llamar iguales a cualquier número con códigos hash coincidentes".

"No, puedes salirte con la tuya con un número mucho menor de comparaciones".

"Imagina que nuestro Set almacena números agrupados u ordenados por hashCode (ordenarlos de esta manera es esencialmente agruparlos, ya que los números con el mismo hashCode estarán uno al lado del otro). Entonces puedes descartar grupos irrelevantes muy rápida y fácilmente. Es suficiente para verificar una vez por grupo para ver si su hashCode coincide con el hashCode del objeto".

"Imagínate que eres un estudiante que busca a un amigo que puedas reconocer de vista y que sabemos que vive en el dormitorio 17. Luego vas a todos los dormitorios de la universidad y preguntas: '¿Es este el dormitorio 17?' Si no es así, entonces ignoras a todos en el dormitorio y pasas al siguiente. Si la respuesta es 'sí', entonces comienzas a caminar por cada una de las habitaciones, buscando a tu amigo".

"En este ejemplo, el número de dormitorio (17) es el código hash".

"Un desarrollador que implemente una función hashCode debe saber lo siguiente:"

A)  dos objetos diferentes pueden tener el mismo código hash  (diferentes personas pueden vivir en el mismo dormitorio)

B)  los objetos que son iguales  ( según el método equalsdeben tener el mismo código hash. .

C)  los códigos hash deben elegirse de modo que no haya muchos objetos diferentes con el mismo código hash.  Si los hay, entonces las ventajas potenciales de los códigos hash se pierden (llegas al dormitorio 17 y descubres que la mitad de la universidad vive allí. ¡Qué fastidio!).

"Y ahora lo más importante. Si anula el método equals , debe anular absolutamente el método hashCode () y cumplir con las tres reglas descritas anteriormente.

"La razón es esta: en Java, los objetos en una colección siempre se comparan/recuperan usando hashCode() antes de compararlos/recuperarlos usando iguales.  Y si objetos idénticos tienen códigos hash diferentes, entonces los objetos se considerarán diferentes y el método de igualdad ni siquiera será llamado.

"En nuestro ejemplo de fracciones, si hacemos que el código hash sea igual al numerador, las fracciones 2/3 y 4/6 tendrían códigos hash diferentes. Las fracciones son iguales y el método de igualdad dice que son iguales, pero sus códigos hash dicen son diferentes. Y si comparamos usando hashCode antes de comparar usando iguales, entonces concluimos que los objetos son diferentes y ni siquiera llegamos al método de iguales".

Aquí hay un ejemplo:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Si el método hashCode()  devuelve el numerador de las fracciones, el resultado será  falso .
Y el objeto «nueva Fracción(4,6) » no se encontrará en la colección.

"Entonces, ¿cuál es la forma correcta de implementar hashCode para fracciones?"

"Aquí debe recordar que las fracciones equivalentes deben tener el mismo código hash".

" Versión 1 : el hashCode es igual al resultado de la división de enteros".

"Para 7/5 y 6/5, esto sería 1".

"Para 4/5 y 3/5, esto sería 0".

"Pero esta opción no es adecuada para comparar fracciones que son deliberadamente menores que 1. El código hash (resultado de la división de enteros) siempre será 0".

" Versión 2 : el código hash es igual al resultado de la división entera del denominador por el numerador".

"Esta opción es adecuada para casos en los que la fracción es menor que 1. Si la fracción es menor que 1, entonces su inversa es mayor que 1. Y si invertimos todas las fracciones, las comparaciones no se ven afectadas de ninguna manera".

"Nuestra versión final combina ambas soluciones:"

public int hashCode()
{
return numerator/denominator + denominator/numerator;
}

Probémoslo usando 2/3 y 4/6. Deben tener códigos hash idénticos:

Fracción 2/3 Fracción 4/6
numerador denominador 2 / 3 == 0 4 / 6 == 0
denominador / numerador 3 / 2 == 1 6 / 4 == 1
numerador / denominador
+
denominador / numerador
0 + 1 == 1 0 + 1 == 1

"Eso es todo por ahora."

"Gracias, Ellie. Eso fue realmente interesante".