"Ora ti parlerò di alcuni metodi altrettanto utili: equals(Object o) & hashCode() ."
"Probabilmente hai già ricordato che, in Java, quando si confrontano le variabili di riferimento non vengono confrontati gli oggetti stessi, ma piuttosto i riferimenti agli oggetti."
Codice | Spiegazione |
---|---|
|
i non è uguale a j Le variabili puntano a oggetti diversi. Anche se gli oggetti contengono gli stessi dati. |
|
i è uguale a j. Le variabili contengono un riferimento allo stesso oggetto. |
"Sì, me lo ricordo."
Gli uguali .
"Il metodo equals è la soluzione standard qui. Lo scopo del metodo equals è determinare se gli oggetti sono internamente identici confrontando ciò che è memorizzato al loro interno."
"E come fa?""È tutto molto simile al metodo toString()."
La classe Object ha la propria implementazione del metodo equals, che confronta semplicemente i riferimenti:
public boolean equals(Object obj)
{
return (this == obj);
}
"Fantastico... torniamo di nuovo a quello, vero?"
"Tieni il mento alto! In realtà è molto complicato."
"Questo metodo è stato creato per consentire agli sviluppatori di sovrascriverlo nelle proprie classi. Dopotutto, solo lo sviluppatore di una classe sa quali dati sono rilevanti e cosa no durante il confronto."
"Puoi fornire un esempio?"
"Certo. Supponiamo di avere una classe che rappresenta le frazioni matematiche. Avrebbe questo aspetto:"
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;
}
}
Esempio di chiamata al metodo: |
---|
Fraction one = new Fraction(2,3); Fraction two = new Fraction(4,6); System.out.println(one.equals(two)); |
La chiamata al metodo restituirà true. La frazione 2/3 è uguale alla frazione 4/6 |
"Ora, analizziamo questo esempio."
"Abbiamo sovrascritto il metodo equals , quindi gli oggetti Fraction avranno la loro implementazione.
"Ci sono diversi controlli nel metodo:"
" 1) Se l'oggetto passato per il confronto è null , allora gli oggetti non sono uguali. Se puoi chiamare il metodo equals su un oggetto, allora sicuramente non è null ."
" 2) Un confronto tra classi. Se gli oggetti sono istanze di classi diverse, non cercheremo di confrontarli. Invece, useremo immediatamente return false per indicare che si tratta di oggetti diversi."
" 3) Tutti ricordano dalla seconda elementare che 2/3 è uguale a 4/6. Ma come si fa a verificarlo?"
2/3 == 4/6 |
---|
Moltiplichiamo entrambi i lati per entrambi i divisori (6 e 3) e otteniamo: |
6 * 2 == 4 * 3 |
12 == 12 |
Regola generale: |
Se a / b == c / d Allora a * d == c * b |
"Di conseguenza, nella terza parte del metodo equals , trasformiamo l'oggetto passato in una frazione e confrontiamo le frazioni."
"Capito. Se confrontiamo semplicemente il numeratore con il numeratore e il denominatore con il denominatore, allora 2/3 non è uguale a 4/6."
"Ora capisco cosa intendevi quando hai detto che solo lo sviluppatore di una classe sa come confrontarlo correttamente."
"Sì, ma questa è solo metà della storia. C'è un altro metodo: hashCode(). "
"Ora tutto ciò che riguarda il metodo equals ha senso, ma perché abbiamo bisogno di hashCode ()? "
"Il metodo hashCode è necessario per confronti rapidi."
"Il metodo equals ha un grosso svantaggio: funziona troppo lentamente. Supponi di avere un insieme di milioni di elementi e di dover verificare se contiene un oggetto specifico. Come si fa?"
"Potrei scorrere tutti gli elementi usando un ciclo e confrontare l'oggetto con ogni oggetto nel set. Finché non trovo una corrispondenza."
"E se non c'è? Faremmo un milione di confronti solo per scoprire che l'oggetto non c'è? Non ti sembra tanto?"
"Sì, anche io riconosco che sono troppi paragoni. C'è un altro modo?"
"Sì, puoi usare hashCode () per questo.
Il metodo hashCode () restituisce un numero specifico per ogni oggetto. Lo sviluppatore di una classe decide quale numero viene restituito, proprio come fa per il metodo equals.
"Facciamo un esempio:"
"Immagina di avere un milione di numeri a 10 cifre. Quindi, potresti fare in modo che l'hashCode di ogni numero sia il resto dopo aver diviso il numero per 100."
Ecco un esempio:
Numero | Il nostro hashCode |
---|---|
1234567890 | 90 |
9876554321 | 21 |
9876554221 | 21 |
9886554121 | 21 |
"Sì, ha senso. E cosa facciamo con questo hashCode?"
"Invece di confrontare i numeri, confrontiamo i loro hashCodes . È più veloce in questo modo."
"E chiamiamo uguali solo se i loro hashCode sono uguali."
"Sì, è più veloce. Ma dobbiamo ancora fare un milione di confronti. Stiamo solo confrontando numeri più piccoli e dobbiamo ancora chiamare uguali per tutti i numeri con hashCodes corrispondenti."
"No, puoi farla franca con un numero molto minore di confronti."
"Immagina che il nostro Set memorizzi i numeri raggruppati o ordinati per hashCode (ordinarli in questo modo è essenzialmente raggrupparli, poiché i numeri con lo stesso hashCode saranno uno accanto all'altro). Quindi puoi scartare molto rapidamente e facilmente i gruppi non pertinenti. È sufficiente controllare una volta per gruppo per vedere se il suo hashCode corrisponde all'hashCode dell'oggetto."
"Immagina di essere uno studente alla ricerca di un amico che puoi riconoscere di vista e che sappiamo vive nel dormitorio 17. Poi vai in ogni dormitorio dell'università e chiedi: 'Questo è il dormitorio 17?' Se non lo è, ignori tutti nel dormitorio e passi a quello successivo. Se la risposta è 'sì', inizi a passare davanti a ciascuna delle stanze, cercando il tuo amico."
"In questo esempio, il numero del dormitorio (17) è l'hashCode."
"Uno sviluppatore che implementa una funzione hashCode deve conoscere quanto segue:"
A) due oggetti diversi possono avere lo stesso hashCode (persone diverse possono vivere nello stesso dormitorio)
B) gli oggetti uguali ( secondo il metodo equals ) devono avere lo stesso hashCode. .
C) i codici hash devono essere scelti in modo che non ci siano molti oggetti diversi con lo stesso hashCode. Se ci sono, i potenziali vantaggi degli hashcode sono persi (arrivi al dormitorio 17 e scopri che metà dell'università vive lì. Peccato!).
"E ora la cosa più importante. Se si esegue l'override del metodo equals , è assolutamente necessario eseguire l'override del metodo hashCode () e rispettare le tre regole sopra descritte.
"Il motivo è questo: in Java, gli oggetti in una raccolta vengono sempre confrontati/recuperati utilizzando hashCode() prima di essere confrontati/recuperati utilizzando equals. E se oggetti identici hanno hashCode diversi, gli oggetti saranno considerati diversi e il metodo equals non sarà nemmeno chiamato.
"Nel nostro esempio Fraction, se rendiamo hashCode uguale al numeratore, le frazioni 2/3 e 4/6 avrebbero diversi hashCode. Le frazioni sono le stesse e il metodo equals dice che sono uguali, ma i loro hashCode dicono sono diversi. E se confrontiamo usando hashCode prima di confrontare usando equals, allora concludiamo che gli oggetti sono diversi e non arriviamo nemmeno al metodo equals."
Ecco un esempio:
HashSet<Fraction>set = new HashSet<Fraction>(); set.add(new Fraction(2,3)); System.out.println( set.contains(new Fraction(4,6)) ); |
Se il metodo hashCode() restituisce il numeratore delle frazioni, il risultato sarà false . E l'oggetto «new Fraction(4,6) » non sarà trovato nella collezione. |
"Allora qual è il modo giusto per implementare hashCode per le frazioni?"
"Qui devi ricordare che le frazioni equivalenti devono avere lo stesso hashCode."
" Versione 1 : hashCode è uguale al risultato della divisione intera."
"Per 7/5 e 6/5, questo sarebbe 1."
"Per 4/5 e 3/5, questo sarebbe 0."
"Ma questa opzione è poco adatta per confrontare frazioni deliberatamente inferiori a 1. L'hashCode (risultato della divisione intera) sarà sempre 0."
" Versione 2 : hashCode è uguale al risultato della divisione intera del denominatore per il numeratore."
"Questa opzione è adatta per i casi in cui la frazione è minore di 1. Se la frazione è minore di 1, allora il suo inverso è maggiore di 1. E se invertiamo tutte le frazioni, i confronti non sono in alcun modo influenzati."
"La nostra versione finale combina entrambe le soluzioni:"
public int hashCode()
{
return numerator/denominator + denominator/numerator;
}
Proviamo usando 2/3 e 4/6. Dovrebbero avere hashCode identici:
Frazione 2/3 | Frazione 4/6 | |
---|---|---|
numeratore denominatore | 2/3 == 0 | 4/6 == 0 |
denominatore/numeratore | 3/2 == 1 | 6/4 == 1 |
numeratore / denominatore + denominatore / numeratore |
0 + 1 == 1 | 0 + 1 == 1 |
"È tutto per ora."
"Grazie, Ellie. È stato davvero interessante."
GO TO FULL VERSION