"Nu ska jag berätta om några metoder som är lika användbara: equals(Object o) & hashCode() ."
"Du har säkert redan kommit ihåg att i Java, när man jämför referensvariabler jämförs inte objekten själva, utan snarare referenserna till objekten."
Koda | Förklaring |
---|---|
|
i är inte lika med j Variablerna pekar på olika objekt. Även om objekten innehåller samma data. |
|
jag är lika med j. Variablerna innehåller en referens till samma objekt. |
"Ja, det kommer jag ihåg."
De lika .
"Equals-metoden är standardlösningen här. Syftet med equals-metoden är att avgöra om objekt internt är identiska genom att jämföra vad som lagras inuti dem."
"Och hur gör den det?""Det hela är väldigt likt toString()-metoden."
Objektklassen har sin egen implementering av equals-metoden, som helt enkelt jämför referenserna:
public boolean equals(Object obj)
{
return (this == obj);
}
"Bra... tillbaka till det igen, eller hur?"
"Håll hakan uppe! Det är faktiskt väldigt knepigt."
"Denna metod skapades för att utvecklare ska kunna skriva över den i sina egna klasser. När allt kommer omkring är det bara en klass utvecklare som vet vilken data som är relevant och vad som inte är det när man jämför."
"Kan du ge ett exempel?"
"Visst. Anta att vi har en klass som representerar matematiska bråk. Den skulle se ut så här:"
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;
}
}
Exempel på metodanrop: |
---|
Fraction one = new Fraction(2,3); Fraction two = new Fraction(4,6); System.out.println(one.equals(two)); |
Metodanropet kommer att returnera sant. Bråket 2/3 är lika med bråket 4/6 |
"Nu, låt oss dissekera det här exemplet."
"Vi åsidosatte equals -metoden, så Fraction- objekt kommer att ha sin egen implementering.
"Det finns flera kontroller i metoden:"
" 1) Om objektet som skickas för jämförelse är null , då är objekten inte lika. Om du kan anropa equals -metoden på ett objekt så är det definitivt inte null ."
" 2) En klassjämförelse. Om objekten är instanser av olika klasser, kommer vi inte att försöka jämföra dem. Istället kommer vi omedelbart att använda return false för att indikera att dessa är olika objekt."
" 3) Alla kommer ihåg från andra klass att 2/3 är lika med 4/6. Men hur kontrollerar man det?"
2/3 == 4/6 |
---|
Vi multiplicerar båda sidor med båda divisorerna (6 och 3), och vi får: |
6 * 2 == 4 * 3 |
12 == 12 |
Allmän regel: |
Om a / b == c / d Då är a * d == c * b |
"Därför, i den tredje delen av metoden lika , kastar vi det passerade objektet till en bråkdel och jämför bråken."
"Förstår. Om vi helt enkelt jämför täljaren med täljaren och nämnaren med nämnaren, så är 2/3 inte lika med 4/6."
"Nu förstår jag vad du menade när du sa att bara en klass utvecklare vet hur man jämför det korrekt."
"Ja, men det är bara halva historien. Det finns en annan metod: hashCode(). "
"Allt med equals-metoden är vettigt nu, men varför behöver vi hashCode ( )? "
" HashCode- metoden behövs för snabba jämförelser."
" Equals -metoden har en stor nackdel: den fungerar för långsamt. Anta att du har en uppsättning av miljontals element och behöver kontrollera om den innehåller ett specifikt objekt. Hur gör du det?"
"Jag kunde cykla igenom alla element med hjälp av en slinga och jämföra objektet med varje objekt i uppsättningen. Tills jag hittar en matchning."
"Och om det inte finns där? Vi skulle göra en miljon jämförelser bara för att ta reda på att föremålet inte är där? Verkar det inte vara mycket?"
"Ja, till och med jag inser att det är för många jämförelser. Finns det något annat sätt?"
"Ja, du kan använda hashCode () för detta.
Metoden hashCode () returnerar ett specifikt nummer för varje objekt. En klass utvecklare bestämmer vilket nummer som returneras, precis som han eller hon gör för metoden lika.
"Låt oss titta på ett exempel:"
"Föreställ dig att du har en miljon 10-siffriga nummer. Sedan kan du få varje nummers hashCode att vara resten efter att ha dividerat talet med 100."
Här är ett exempel:
siffra | Vår hashkod |
---|---|
1234567890 | 90 |
9876554321 | 21 |
9876554221 | 21 |
9886554121 | 21 |
"Ja, det är vettigt. Och vad gör vi med den här hashkoden?"
"Istället för att jämföra siffrorna jämför vi deras hashkoder . Det går snabbare så."
"Och vi kallar lika bara om deras hashkoder är lika."
"Ja, det är snabbare. Men vi måste fortfarande göra en miljon jämförelser. Vi jämför bara mindre tal, och vi måste fortfarande kalla lika för alla nummer med matchande hashkoder."
"Nej, du kan komma undan med ett mycket mindre antal jämförelser."
"Föreställ dig att vår uppsättning lagrar nummer grupperade eller sorterade efter hashCode (att sortera dem på detta sätt är i huvudsak att gruppera dem, eftersom nummer med samma hashCode kommer att ligga bredvid varandra). Då kan du mycket snabbt och enkelt kassera irrelevanta grupper. Det räcker för att kontrollera en gång per grupp för att se om dess hashCode matchar objektets hashCode."
"Föreställ dig att du är en student som letar efter en vän du kan känna igen på synen och som vi vet bor i Dorm 17. Sedan går du bara till varje studenthem på universitetet och frågar "Är det här Dorm 17?" Om det inte är det, så ignorerar du alla i studentrummet och går vidare till nästa. Om svaret är 'ja' börjar du gå förbi vart och ett av rummen och leta efter din vän."
"I det här exemplet är studentrummets nummer (17) hashkoden."
"En utvecklare som implementerar en hashCode-funktion måste känna till följande:"
A) två olika objekt kan ha samma hashCode (olika personer kan bo i samma sovsal)
B) objekt som är lika ( enligt equals-metoden ) måste ha samma hashCode. .
C) hashkoder måste väljas så att det inte finns många olika objekt med samma hashCode. Om det finns det går hashkoders potentiella fördelar förlorade (du kommer till Dorm 17 och upptäcker att halva universitetet bor där. Bummer!).
"Och nu det viktigaste. Om du åsidosätter equals- metoden måste du absolut åsidosätta hashCode ()-metoden och följa de tre reglerna som beskrivs ovan.
"Anledningen är detta: i Java jämförs/hämtas objekt i en samling alltid med hashCode() innan de jämförs/hämtas med lika. Och om identiska objekt har olika hashkoder, så kommer objekten att betraktas som olika och likametoden kommer inte ens att bli uppringd.
"I vårt exempel på bråk, om vi gjorde hashkoden lika med täljaren, skulle bråken 2/3 och 4/6 ha olika hashkoder. Bråken är desamma och likametoden säger att de är lika, men deras hashkoder säger de är olika. Och om vi jämför med hashCode innan vi jämför med lika, drar vi slutsatsen att objekten är olika och att vi aldrig ens når lika med metoden."
Här är ett exempel:
HashSet<Fraction>set = new HashSet<Fraction>(); set.add(new Fraction(2,3)); System.out.println( set.contains(new Fraction(4,6)) ); |
Om metoden hashCode() returnerar bråkens täljare, blir resultatet false . Och det «nya fraktion(4,6) »-objektet kommer inte att hittas i samlingen. |
"Så vad är det rätta sättet att implementera hashCode för fraktioner?"
"Här måste du komma ihåg att motsvarande bråk måste ha samma hashCode."
" Version 1 : hashkoden är lika med resultatet av heltalsdivision."
"För 7/5 och 6/5 skulle detta vara 1."
"För 4/5 och 3/5 skulle detta vara 0."
"Men det här alternativet är dåligt lämpat för att jämföra bråk som är medvetet mindre än 1. HashCoden (resultatet av heltalsdivision) kommer alltid att vara 0."
" Version 2 : hashCoden är lika med resultatet av heltals division av nämnaren med täljaren."
"Det här alternativet är lämpligt för tillfällen där bråktalet är mindre än 1. Om bråktalet är mindre än 1, är dess invers större än 1. Och om vi inverterar alla bråken, så påverkas inte jämförelserna på något sätt."
"Vår slutversion kombinerar båda lösningarna:"
public int hashCode()
{
return numerator/denominator + denominator/numerator;
}
Låt oss testa det med 2/3 och 4/6. De bör ha identiska hashkoder:
Bråk 2/3 | Bråk 4/6 | |
---|---|---|
täljare / nämnare | 2/3 == 0 | 4/6 == 0 |
nämnare / täljare | 3/2 == 1 | 6/4 == 1 |
täljare / nämnare + nämnare / täljare |
0 + 1 == 1 | 0 + 1 == 1 |
"Det var allt tills vidare."
"Tack, Ellie. Det var verkligen intressant."
GO TO FULL VERSION