"Nu vil jeg fortælle dig om nogle metoder, der er lige så nyttige:  equals(Object o) & hashCode() ."

"Du har sikkert allerede husket, at i Java, når man sammenligner referencevariabler, bliver selve objekterne ikke sammenlignet, men snarere referencerne til objekterne."

Kode Forklaring
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i er ikke lig med j
Variablerne peger på forskellige objekter.
Også selvom objekterne indeholder de samme data.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
jeg er lig med j. Variablerne indeholder en reference til det samme objekt.

"Ja, det kan jeg huske."

De  ligeværdige .

"Lig-metoden er standardløsningen her. Formålet med lig-metoden er at bestemme, om objekter internt er identiske ved at sammenligne, hvad der er gemt inde i dem."

"Og hvordan gør den det?"

"Det hele ligner meget toString()-metoden."

Object-klassen har sin egen implementering af equals-metoden, som blot sammenligner referencerne:

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

"Fantastisk... tilbage til det igen, er vi?"

"Hold hagen oppe! Det er faktisk meget tricky."

"Denne metode blev skabt for at give udviklere mulighed for at overskrive den i deres egne klasser. Det er trods alt kun en klasses udvikler, der ved, hvilke data der er relevante, og hvad der ikke er, når de sammenligner."

"Kan du give et eksempel?"

"Selvfølgelig. Antag, at vi har en klasse, der repræsenterer matematiske brøker. Den ville se sådan ud:"

Eksempel:
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;
}
}
Eksempel på metodekald:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
Metodekaldet returnerer true.
Brøken 2/3 er lig med brøkdelen 4/6

"Nu, lad os dissekere dette eksempel."

"Vi tilsidesatte equals- metoden, så fraktionsobjekter vil have deres egen implementering.

"Der er flere kontroller i metoden:"

" 1)  Hvis objektet, der sendes til sammenligning er null , så er objekterne ikke ens. Hvis du kan kalde equals- metoden på et objekt, så er det bestemt ikke null ."

" 2)  En klassesammenligning. Hvis objekterne er forekomster af forskellige klasser, så vil vi ikke forsøge at sammenligne dem. I stedet vil vi straks bruge return false for at indikere, at disse er forskellige objekter."

" 3)  Alle husker fra anden klasse, at 2/3 er lig med 4/6. Men hvordan tjekker man det?"

2/3 == 4/6
Vi multiplicerer begge sider med begge divisorer (6 og 3), og vi får:
6 * 2 == 4 * 3
12 == 12
Generel regel:
Hvis
a / b == c / d

a * d == c * b

"I den tredje del af lighedsmetoden kaster vi derfor det passerede objekt til en brøk og sammenligner brøkerne."

"Godt det. Hvis vi blot sammenligner tælleren med tælleren og nævneren med nævneren, så er 2/3 ikke lig med 4/6."

"Nu forstår jeg, hvad du mente, da du sagde, at kun en klasses udvikler ved, hvordan man sammenligner det korrekt."

"Ja, men det er kun halvdelen af ​​historien.  Der er en anden metode: hashCode(). "

"Alt om equals-metoden giver mening nu, men hvorfor har vi brug for  hashCode ()? "

" HashCode- metoden er nødvendig for hurtige sammenligninger."

" Lig- metoden har en stor ulempe: den virker for langsomt. Antag, at du har et sæt på millioner af elementer og skal tjekke, om det indeholder et specifikt objekt. Hvordan gør du det?"

"Jeg kunne cykle gennem alle elementerne ved hjælp af en løkke og sammenligne objektet med hvert objekt i sættet. Indtil jeg finder en match."

"Og hvis det ikke er der? Vi ville udføre en million sammenligninger bare for at finde ud af, at objektet ikke er der? Synes det ikke er meget?"

"Ja, selv jeg erkender, at det er for mange sammenligninger. Er der en anden måde?"

"Ja, du kan bruge hashCode () til dette.

Metoden hashCode () returnerer et specifikt tal for hvert objekt . En klasses udvikler bestemmer, hvilket tal der returneres, ligesom han eller hun gør for equals-metoden.

"Lad os se på et eksempel:"

"Forestil dig, at du har en million 10-cifrede tal. Så kan du få hvert tals hashCode til at være resten efter at have divideret tallet med 100."

Her er et eksempel:

Nummer Vores hashkode
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Ja, det giver mening. Og hvad gør vi med denne hashCode?"

"I stedet for at sammenligne tallene, sammenligner vi deres hashCodes . Det er hurtigere på den måde."

"Og vi kalder kun lige , hvis deres hashkoder er ens."

"Ja, det er hurtigere. Men vi skal stadig lave en million sammenligninger. Vi sammenligner bare mindre tal, og vi skal stadig kalde lige for alle tal med matchende hashkoder."

"Nej, du kan slippe afsted med et meget mindre antal sammenligninger."

"Forestil dig, at vores sæt gemmer numre grupperet eller sorteret efter hashCode (at sortere dem på denne måde er i bund og grund at gruppere dem, da numre med samme hashCode vil være ved siden af ​​hinanden). Så kan du meget hurtigt og nemt kassere irrelevante grupper. Det er nok for at kontrollere én gang pr. gruppe for at se, om dens hashCode matcher objektets hashCode."

"Forestil dig, at du er studerende på udkig efter en ven, du kan genkende på synet, og som vi ved bor i Dorm 17. Så går du bare til alle kollegier på universitetet og spørger: 'Er dette Dorm 17?' Hvis det ikke er det, så ignorerer du alle på kollegiet og går videre til det næste. Hvis svaret er 'ja', så begynder du at gå forbi hvert af værelserne og lede efter din ven."

"I dette eksempel er kollegienummeret (17) hashkoden."

"En udvikler, der implementerer en hashCode-funktion, skal vide følgende:"

A)  to forskellige objekter kan have den samme hashCode  (forskellige mennesker kan bo i den samme sovesal)

B)  objekter, der er ens  ( ifølge equals-metodenskal have samme hashCode. .

C)  hash-koder skal vælges, så der ikke er mange forskellige objekter med samme hashCode.  Hvis der er, så går hashkoders potentielle fordele tabt (du kommer til Dorm 17 og opdager, at halvdelen af ​​universitetet bor der. Bummer!).

"Og nu det vigtigste. Hvis du tilsidesætter equals- metoden, skal du absolut tilsidesætte hashCode ()-metoden og overholde de tre regler beskrevet ovenfor.

"Årsagen er denne: i Java bliver objekter i en samling altid sammenlignet/hentet ved hjælp af hashCode(), før de sammenlignes/hentes ved hjælp af equals.  Og hvis identiske objekter har forskellige hashCodes, så vil objekterne blive betragtet som forskellige og lig med metoden. vil ikke engang blive kaldt.

"I vores brøkeksempel, hvis vi gjorde hashkoden lig med tælleren, ville brøkerne 2/3 og 4/6 have forskellige hashkoder. Brøkerne er de samme, og lighedsmetoden siger, at de er de samme, men deres hashkoder siger de er forskellige. Og hvis vi sammenligner ved at bruge hashCode, før vi sammenligner ved at bruge equals, så konkluderer vi, at objekterne er forskellige, og vi når aldrig engang til equals-metoden."

Her er et eksempel:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Hvis hashCode()  -metoden returnerer brøkernes tæller, vil resultatet være  falsk .
Og det «nye Brøk(4,6) »-objekt vil ikke blive fundet i samlingen.

"Så hvad er den rigtige måde at implementere hashCode for fraktioner?"

"Her skal du huske, at tilsvarende brøker skal have samme hashCode."

" Version 1 : hashkoden er lig med resultatet af heltalsdeling."

"For 7/5 og 6/5 ville dette være 1."

"For 4/5 og 3/5 ville dette være 0."

"Men denne mulighed er dårligt egnet til at sammenligne brøker, der bevidst er mindre end 1. HashCoden (resultatet af heltalsdeling) vil altid være 0."

" Version 2 : hashkoden er lig med resultatet af heltals division af nævneren med tælleren."

"Denne mulighed er velegnet til tilfælde, hvor brøken er mindre end 1. Hvis brøken er mindre end 1, så er dens inverse større end 1. Og hvis vi inverterer alle brøkerne, så påvirkes sammenligningerne på ingen måde."

"Vores endelige version kombinerer begge løsninger:"

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

Lad os teste det med 2/3 og 4/6. De skal have identiske hashkoder:

Brøk 2/3 Brøk 4/6
tæller / nævner 2/3 == 0 4/6 == 0
nævner / tæller 3/2 == 1 6/4 == 1
tæller / nævner
+
nævner / tæller
0 + 1 == 1 0 + 1 == 1

"Det er alt for nu."

"Tak, Ellie. Det var virkelig interessant."