"Maintenant, je vais vous parler de quelques méthodes tout aussi utiles :  equals(Object o) & hashCode() ."

"Vous vous êtes probablement déjà rappelé qu'en Java, lors de la comparaison de variables de référence, les objets eux-mêmes ne sont pas comparés, mais plutôt les références aux objets."

Code Explication
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i n'est pas égal à j
Les variables pointent vers des objets différents.
Même si les objets contiennent les mêmes données.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
i est égal à j. Les variables contiennent une référence au même objet.

"Oui, je m'en souviens."

Les  égaux .

"La méthode equals est la solution standard ici. Le but de la méthode equals est de déterminer si les objets sont identiques en interne en comparant ce qui est stocké à l'intérieur."

« Et comment fait-il cela ?

"Tout est très similaire à la méthode toString()."

La classe Object a sa propre implémentation de la méthode equals, qui compare simplement les références :

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

"Génial... revenons à ça, n'est-ce pas ?"

"Garde la tête haute ! C'est en fait très délicat."

"Cette méthode a été créée pour permettre aux développeurs de l'écraser dans leurs propres classes. Après tout, seul le développeur d'une classe sait quelles données sont pertinentes et ce qui ne l'est pas lors de la comparaison."

"Pouvez vous donner un exemple?"

"Bien sûr. Supposons que nous ayons une classe qui représente des fractions mathématiques. Cela ressemblerait à ceci :"

Exemple:
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;
}
}
Exemple d'appel de méthode :
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
L'appel de méthode renverra vrai.
La fraction 2/3 est égale à la fraction 4/6

"Maintenant, disséquons cet exemple."

"Nous avons remplacé la méthode equals , donc les objets Fraction auront leur propre implémentation.

"Il y a plusieurs vérifications dans la méthode :"

" 1)  Si l'objet passé pour comparaison est null , alors les objets ne sont pas égaux. Si vous pouvez appeler la méthode equals sur un objet, alors ce n'est certainement pas null ."

" 2)  Une comparaison de classes. Si les objets sont des instances de classes différentes, nous n'essaierons pas de les comparer. Au lieu de cela, nous utiliserons immédiatement return false pour indiquer qu'il s'agit d'objets différents."

" 3)  Tout le monde se souvient dès le CE2 que 2/3 est égal à 4/6. Mais comment vérifier cela ?"

2/3 == 4/6
On multiplie les deux côtés par les deux diviseurs (6 et 3), et on obtient :
6 * 2 == 4 * 3
12 == 12
Règle générale:
Si
a / b == c / d
Alors
a * d == c * b

"En conséquence, dans la troisième partie de la méthode equals , nous transformons l'objet passé en Fraction et comparons les fractions."

"Compris. Si nous comparons simplement le numérateur avec le numérateur et le dénominateur avec le dénominateur, alors 2/3 n'est pas égal à 4/6."

"Maintenant, je comprends ce que vous vouliez dire lorsque vous disiez que seul le développeur d'une classe sait comment la comparer correctement."

"Oui, mais ce n'est que la moitié de l'histoire.  Il existe une autre méthode : hashCode(). "

"Tout ce qui concerne la méthode equals a du sens maintenant, mais pourquoi avons-nous besoin  de hashCode () ? "

"La méthode hashCode est nécessaire pour des comparaisons rapides."

"La méthode equals a un inconvénient majeur : elle fonctionne trop lentement. Supposons que vous ayez un ensemble de millions d'éléments et que vous ayez besoin de vérifier s'il contient un objet spécifique. Comment faites-vous cela ?"

"Je pourrais faire défiler tous les éléments à l'aide d'une boucle et comparer l'objet avec chaque objet de l'ensemble. Jusqu'à ce que je trouve une correspondance."

« Et s'il n'est pas là ? Nous effectuerions un million de comparaisons juste pour découvrir que l'objet n'est pas là ? Cela ne semble pas beaucoup ?

"Oui, même moi je reconnais que c'est trop de comparaisons. Y a-t-il un autre moyen ?"

"Oui, vous pouvez utiliser hashCode () pour cela.

La méthode hashCode () renvoie un nombre spécifique pour chaque objet. Le développeur d'une classe décide du nombre renvoyé, tout comme il le fait pour la méthode equals.

"Regardons un exemple :"

"Imaginez que vous ayez un million de numéros à 10 chiffres. Ensuite, vous pourriez faire en sorte que le hashCode de chaque numéro soit le reste après avoir divisé le nombre par 100."

Voici un exemple :

Nombre Notre hashCode
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Ouais, c'est logique. Et qu'est-ce qu'on fait avec ce hashCode ?"

"Au lieu de comparer les chiffres, nous comparons leurs hashCodes . C'est plus rapide de cette façon."

"Et nous n'appelons égaux que si leurs hashCodes sont égaux."

"Ouais, c'est plus rapide. Mais nous devons encore faire un million de comparaisons. Nous comparons juste des nombres plus petits, et nous devons toujours appeler des égaux pour tous les nombres avec des hashCodes correspondants."

"Non, vous pouvez vous en tirer avec un nombre beaucoup plus petit de comparaisons."

"Imaginez que notre Set stocke des nombres regroupés ou triés par hashCode (les trier de cette manière revient essentiellement à les regrouper, car les nombres avec le même hashCode seront côte à côte). Ensuite, vous pouvez très rapidement et facilement supprimer les groupes non pertinents. C'est assez pour vérifier une fois par groupe si son hashCode correspond au hashCode de l'objet."

"Imaginez que vous êtes un étudiant à la recherche d'un ami que vous pouvez reconnaître de vue et dont nous savons qu'il vit dans le dortoir 17. Ensuite, vous vous rendez dans tous les dortoirs de l'université et demandez : 'Est-ce que c'est le dortoir 17 ?' Si ce n'est pas le cas, alors vous ignorez tout le monde dans le dortoir et passez au suivant. Si la réponse est "oui", alors vous commencez à passer devant chacune des chambres, à la recherche de votre ami."

"Dans cet exemple, le numéro de dortoir (17) est le hashCode."

"Un développeur qui implémente une fonction hashCode doit savoir ce qui suit :"

A)  deux objets différents peuvent avoir le même hashCode  (différentes personnes peuvent vivre dans le même dortoir)

B)  les objets identiques  ( selon la méthode equalsdoivent avoir le même hashCode. .

C)  les codes de hachage doivent être choisis de sorte qu'il n'y ait pas beaucoup d'objets différents avec le même hashCode.  S'il y en a, alors les avantages potentiels des hashcodes sont perdus (vous arrivez au dortoir 17 et constatez que la moitié de l'université y vit. Dommage !).

"Et maintenant le plus important. Si vous redéfinissez la méthode equals , vous devez absolument redéfinir la méthode hashCode () et respecter les trois règles décrites ci-dessus.

"La raison est la suivante : en Java, les objets d'une collection sont toujours comparés/récupérés à l'aide de hashCode() avant d'être comparés/récupérés à l'aide d'égal.  Et si des objets identiques ont des hashCodes différents, alors les objets seront considérés comme différents et la méthode equals ne sera même pas appelé.

"Dans notre exemple Fraction, si nous rendions le hashCode égal au numérateur, les fractions 2/3 et 4/6 auraient des hashCodes différents. Les fractions sont identiques et la méthode equals indique qu'elles sont identiques, mais leurs hashCodes indiquent ils sont différents. Et si nous comparons en utilisant hashCode avant de comparer en utilisant equals, alors nous concluons que les objets sont différents et nous n'arrivons même jamais à la méthode equals.

Voici un exemple :

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Si la méthode hashCode()  renvoie le numérateur des fractions, le résultat sera  false .
Et l'objet « new Fraction(4,6) » ne sera pas trouvé dans la collection.

"Alors, quelle est la bonne façon d'implémenter hashCode pour les fractions?"

"Ici, vous devez vous rappeler que les fractions équivalentes doivent avoir le même hashCode."

" Version 1 : le hashCode est égal au résultat de la division entière."

"Pour 7/5 et 6/5, ce serait 1."

"Pour 4/5 et 3/5, ce serait 0."

"Mais cette option est mal adaptée pour comparer des fractions délibérément inférieures à 1. Le hashCode (résultat de la division entière) sera toujours 0."

" Version 2 : le hashCode est égal au résultat de la division entière du dénominateur par le numérateur."

"Cette option convient aux cas où la fraction est inférieure à 1. Si la fraction est inférieure à 1, alors son inverse est supérieur à 1. Et si nous inversons toutes les fractions, les comparaisons ne sont en aucun cas affectées."

"Notre version finale combine les deux solutions :"

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

Testons-le en utilisant 2/3 et 4/6. Ils doivent avoir des hashCodes identiques :

fraction 2/3 fraction 4/6
Numérateur dénominateur 2 / 3 == 0 4 / 6 == 0
dénominateur / numérateur 3 / 2 == 1 6 / 4 == 1
numérateur / dénominateur
+
dénominateur / numérateur
0 + 1 == 1 0 + 1 == 1

"C'est tout pour le moment."

"Merci, Ellie. C'était vraiment intéressant."