"Agora vou falar sobre alguns métodos que são igualmente úteis:  equals(Object o) & hashCode() ."

"Você provavelmente já deve ter lembrado que, em Java, ao comparar variáveis ​​de referência não são comparados os próprios objetos, mas sim as referências aos objetos."

Código Explicação
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i não é igual a j
As variáveis ​​apontam para objetos diferentes.
Mesmo que os objetos contenham os mesmos dados.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
i é igual a j. As variáveis ​​contêm uma referência ao mesmo objeto.

"Sim, eu me lembro disso."

Os  iguais .

"O método equals é a solução padrão aqui. O objetivo do método equals é determinar se os objetos são internamente idênticos comparando o que está armazenado dentro deles."

"E como ele faz isso?"

"É tudo muito parecido com o método toString()."

A classe Object tem sua própria implementação do método equals, que simplesmente compara as referências:

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

"Ótimo... de volta a isso de novo, não é?"

"Mantenha o queixo erguido! Na verdade, é muito complicado."

"Este método foi criado para permitir que os desenvolvedores o sobrescrevam em suas próprias classes. Afinal, apenas o desenvolvedor de uma classe sabe quais dados são relevantes e quais não são ao comparar."

"Você pode dar um exemplo?"

"Claro. Suponha que temos uma classe que representa frações matemáticas. Seria assim:"

Exemplo:
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;
}
}
Exemplo de chamada de método:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
A chamada do método retornará true.
A fração 2/3 é igual à fração 4/6

"Agora, vamos dissecar este exemplo."

"Substituímos o método equals , então os objetos Fraction terão sua própria implementação.

"Existem várias verificações no método:"

" 1)  Se o objeto passado para comparação for null , então os objetos não são iguais. Se você pode chamar o método equals em um objeto, então definitivamente não é null ."

" 2)  Uma comparação de classes. Se os objetos forem instâncias de classes diferentes, não tentaremos compará-los. Em vez disso, usaremos imediatamente return false para indicar que são objetos diferentes."

" 3)  Todo mundo lembra desde a segunda série que 2/3 é igual a 4/6. Mas como você verifica isso?"

2/3 == 4/6
Multiplicamos ambos os lados por ambos os divisores (6 e 3) e obtemos:
6 * 2 == 4 * 3
12 == 12
Regra geral:
Se
a / b == c / d
Então
a * d == c * b

"Conseqüentemente, na terceira parte do método equals , convertemos o objeto passado em uma fração e comparamos as frações."

"Entendi. Se simplesmente compararmos o numerador com o numerador e o denominador com o denominador, então 2/3 não é igual a 4/6."

"Agora entendo o que você quis dizer quando disse que apenas o desenvolvedor de uma classe sabe compará-la corretamente."

"Sim, mas isso é apenas metade da história.  Existe outro método: hashCode(). "

"Tudo sobre o método equals faz sentido agora, mas por que precisamos de  hashCode ()? "

"O método hashCode é necessário para comparações rápidas."

"O método equals tem uma grande desvantagem: ele funciona muito lentamente. Suponha que você tenha um conjunto de milhões de elementos e precise verificar se ele contém um objeto específico. Como você faz isso?"

"Eu poderia percorrer todos os elementos usando um loop e comparar o objeto com cada objeto do conjunto. Até encontrar uma correspondência."

"E se não estiver lá? Faríamos um milhão de comparações só para descobrir que o objeto não está lá? Isso não parece muito?"

"Sim, até eu reconheço que são muitas comparações. Existe outra maneira?"

"Sim, você pode usar hashCode () para isso.

O método hashCode () retorna um número específico para cada objeto. O desenvolvedor de uma classe decide qual número é retornado, assim como ele ou ela faz para o método equals.

"Vejamos um exemplo:"

"Imagine que você tem um milhão de números de 10 dígitos. Em seguida, você pode fazer com que o hashCode de cada número seja o restante após dividir o número por 100."

Aqui está um exemplo:

Número Nosso hashCode
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Sim, isso faz sentido. E o que fazemos com este hashCode?"

"Em vez de comparar os números, comparamos seus hashCodes . É mais rápido assim."

"E nós chamamos iguais apenas se seus hashCodes forem iguais."

"Sim, isso é mais rápido. Mas ainda temos que fazer um milhão de comparações. Estamos apenas comparando números menores e ainda temos que chamar de igual para quaisquer números com hashCodes correspondentes."

"Não, você pode se safar com um número muito menor de comparações."

"Imagine que nosso Set armazene números agrupados ou classificados por hashCode (classificá-los dessa maneira é essencialmente agrupá-los, pois números com o mesmo hashCode estarão próximos uns dos outros). Então você pode descartar grupos irrelevantes de maneira muito rápida e fácil. É o suficiente para verificar uma vez por grupo para ver se seu hashCode corresponde ao hashCode do objeto."

"Imagine que você é um estudante procurando por um amigo que você pode reconhecer de vista e que sabemos que mora no dormitório 17. Então você simplesmente vai a todos os dormitórios da universidade e pergunta: 'Este é o dormitório 17?' Se não for, então você ignora todos no dormitório e passa para o próximo. Se a resposta for 'sim', então você começa a passar por cada um dos quartos, procurando por seu amigo."

"Neste exemplo, o número do dormitório (17) é o hashCode."

"Um desenvolvedor que implementa uma função hashCode deve saber o seguinte:"

A)  dois objetos diferentes podem ter o mesmo hashCode  (pessoas diferentes podem morar no mesmo dormitório)

B)  objetos iguais  ( de acordo com o método equalsdevem ter o mesmo hashCode. .

C)  os códigos hash devem ser escolhidos para que não haja muitos objetos diferentes com o mesmo hashCode.  Se houver, as vantagens potenciais dos hashcodes serão perdidas (você chega ao dormitório 17 e descobre que metade da universidade mora lá. Que chatice!).

"E agora a coisa mais importante. Se você substituir o método equals , você absolutamente deve substituir o método hashCode () e cumprir as três regras descritas acima.

"A razão é esta: em Java, objetos em uma coleção são sempre comparados/recuperados usando hashCode() antes de serem comparados/recuperados usando equals.  E se objetos idênticos tiverem hashCodes diferentes, então os objetos serão considerados diferentes e o método equals nem será chamado.

"Em nosso exemplo de fração, se fizermos o hashCode igual ao numerador, as frações 2/3 e 4/6 teriam hashCodes diferentes. As frações são as mesmas e o método equals diz que são iguais, mas seus hashCodes dizem eles são diferentes. E se compararmos usando hashCode antes de compararmos usando equals, então concluímos que os objetos são diferentes e nunca chegamos ao método equals."

Aqui está um exemplo:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Se o método hashCode()  retornar o numerador das frações, o resultado será  false .
E o objeto «new Fraction(4,6) » não será encontrado na coleção.

"Então, qual é a maneira certa de implementar hashCode para frações?"

"Aqui você precisa lembrar que frações equivalentes devem ter o mesmo hashCode."

" Versão 1 : o hashCode é igual ao resultado da divisão inteira."

"Para 7/5 e 6/5, isso seria 1."

"Para 4/5 e 3/5, isso seria 0."

"Mas esta opção é pouco adequada para comparar frações que são deliberadamente menores que 1. O hashCode (resultado da divisão inteira) sempre será 0."

" Versão 2 : o hashCode é igual ao resultado da divisão inteira do denominador pelo numerador."

"Esta opção é adequada para casos em que a fração é menor que 1. Se a fração for menor que 1, então seu inverso é maior que 1. E se invertermos todas as frações, as comparações não serão afetadas."

"Nossa versão final combina as duas soluções:"

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

Vamos testá-lo usando 2/3 e 4/6. Eles devem ter hashCodes idênticos:

Fração 2/3 Fração 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

"É tudo por agora."

"Obrigado, Ellie. Isso foi muito interessante."