Perché devo sovrascrivere i metodi equals e hashCode in Java?


383

Di recente ho letto questo documento di Developer Works .

Il documento riguarda la definizione hashCode() e equals()in modo efficace e corretto, tuttavia non sono in grado di capire il motivo per cui abbiamo bisogno di ignorare questi due metodi.

Come posso prendere la decisione di implementare questi metodi in modo efficiente?


4
Ci sono due grandi articoli su Programming.guide che spiegano esattamente questo: quando dovrei scavalcare uguale? e perché dovresti sempre sovrascrivere hashCode quando sovrascrivi uguale a . (Attenzione, la risposta accettata è effettivamente sbagliata.)
aioobe,

L'override del caso è uguale solo a: due oggetti uguali avranno hashcode diversi = gli stessi oggetti vanno in un bucket diverso (duplicazione). Caso Sostituisci solo hashcode: due stessi oggetti avranno lo stesso hashcode = lo stesso oggetto va nello stesso bucket (duplicazione).
VdeX,

Risposte:


524

Joshua Bloch dice su Java efficace

È necessario sovrascrivere hashCode () in ogni classe che sovrascrive equals (). In caso contrario, si verificherà una violazione del contratto generale per Object.hashCode (), che impedirà alla classe di funzionare correttamente insieme a tutte le raccolte basate su hash, inclusi HashMap, HashSet e Hashtable.

Proviamo a capirlo con un esempio di cosa accadrebbe se eseguiamo l'override equals()senza eseguire l' override hashCode()e proviamo a utilizzare a Map.

Supponiamo che abbiamo una classe come questa e che due oggetti di MyClasssiano uguali se il loro importantFieldè uguale (con hashCode()e equals()generato da eclipse)

public class MyClass {

    private final String importantField;
    private final String anotherField;

    public MyClass(final String equalField, final String anotherField) {
        this.importantField = equalField;
        this.anotherField = anotherField;
    }

    public String getEqualField() {
        return importantField;
    }

    public String getAnotherField() {
        return anotherField;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((importantField == null) ? 0 : importantField.hashCode());
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final MyClass other = (MyClass) obj;
        if (importantField == null) {
            if (other.importantField != null)
                return false;
        } else if (!importantField.equals(other.importantField))
            return false;
        return true;
    }

}

Sostituisci solo equals

Se solo equalsviene sovrascritto, allora quando chiami per myMap.put(first,someValue)primo passerai un hash a qualche bucket e quando lo chiami myMap.put(second,someOtherValue)andrà a un altro bucket (dato che ne hanno uno diverso hashCode). Quindi, sebbene siano uguali, poiché non hanno hash nello stesso bucket, la mappa non può realizzarlo ed entrambi rimangono nella mappa.


Sebbene non sia necessario eseguire l'override equals()se eseguiamo l'override hashCode(), vediamo cosa succederebbe in questo caso particolare in cui sappiamo che due oggetti di MyClasssono uguali se il loro importantFieldè uguale ma non eseguiamo l'override equals().

Sostituisci solo hashCode

Immagina di avere questo

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

Se si esegue l' override solo hashCodequando lo si chiama myMap.put(first,someValue)per primo, lo calcola hashCodee lo memorizza in un determinato bucket. Quindi quando si chiama myMap.put(second,someOtherValue)dovrebbe sostituire prima con il secondo come da documentazione mappa perché sono uguali (in base ai requisiti aziendali).

Ma il problema è che uguali non sono stati ridefiniti, in modo che quando gli hash mappa seconde scorre il secchio alla ricerca, se c'è un oggetto kin modo tale che second.equals(k)è vero non troverà alcun come second.equals(first)sarà false.

Spero fosse chiaro


5
puoi per favore elaborare un po 'di più, nel secondo caso, perché il secondo oggetto deve andare in un altro bucket?
Hussain Akhtar Wahid 'Ghouri',

57
Non mi piace questa risposta perché suggerisce che non puoi sovrascrivere hashCode () senza sovrascrivere equals (), che semplicemente non è vero. Dici che il tuo codice di esempio (la parte "override only hashCode") non funzionerà perché definisci i tuoi due oggetti uguali, ma - scusa - questa definizione è solo nella tua testa. Nel tuo primo esempio hai due oggetti disuguali con lo stesso hashCode, e questo è perfettamente legale. Quindi il motivo per cui devi sovrascrivere equals () non è perché hai già ignorato hashCode (), ma perché vuoi spostare la tua definizione "uguale a" dalla tua testa al codice.
user2543253

11
if you think you need to override one, then you need to override both of themè sbagliato. È necessario eseguire l'override hashCodese la classe ha la precedenza equalsma il contrario non è vero.
akhil_mittal,

4
Penso che sia del tutto ok sovrascrivere solo hashCode () senza sovrascrivere equals (). È anche scritto in Effective Java : books.google.fr/…
Johnny il

2
@PhantomReference, nota che solo l'override equalsviolerebbe il contratto enunciato nel javadoc di Object: "Se due oggetti sono uguali secondo il equals(Object)metodo, allora chiamare il hashCodemetodo su ciascuno dei due oggetti deve produrre lo stesso risultato intero." Certo, non tutte le parti di tutti i contratti sono esercitate in tutto il codice, ma comunque, formalmente parlando è una violazione e considererei che sia un bug in attesa di accadere.
Aioobe,

264

Collezioni come HashMape HashSetutilizzano un valore hashcode di un oggetto per determinare come deve essere memorizzato all'interno di una raccolta e l' hashcode viene riutilizzato per individuare l'oggetto nella sua raccolta.

Il recupero dell'hash è un processo in due passaggi:

  1. Trova il secchio giusto (usando hashCode())
  2. Cerca nel bucket l'elemento giusto (usando equals())

Ecco un piccolo esempio sul perché dovremmo ignorare equals()e hashcode().

Considera una Employeeclasse che ha due campi: età e nome.

public class Employee {

    String name;
    int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof Employee))
            return false;
        Employee employee = (Employee) obj;
        return employee.getAge() == this.getAge()
                && employee.getName() == this.getName();
    }

    // commented    
    /*  @Override
        public int hashCode() {
            int result=17;
            result=31*result+age;
            result=31*result+(name!=null ? name.hashCode():0);
            return result;
        }
     */
}

Ora crea una classe, inserisci l' Employeeoggetto in a HashSete verifica se l'oggetto è presente o meno.

public class ClientTest {
    public static void main(String[] args) {
        Employee employee = new Employee("rajeev", 24);
        Employee employee1 = new Employee("rajeev", 25);
        Employee employee2 = new Employee("rajeev", 24);

        HashSet<Employee> employees = new HashSet<Employee>();
        employees.add(employee);
        System.out.println(employees.contains(employee2));
        System.out.println("employee.hashCode():  " + employee.hashCode()
        + "  employee2.hashCode():" + employee2.hashCode());
    }
}

Stampa quanto segue:

false
employee.hashCode():  321755204  employee2.hashCode():375890482

Ora hashcode()metodo di commento , esegui lo stesso e l'output sarebbe:

true
employee.hashCode():  -938387308  employee2.hashCode():-938387308

Ora puoi capire perché se due oggetti sono considerati uguali, anche i loro hashcode devono essere uguali? Altrimenti, non saresti mai in grado di trovare l'oggetto poiché il metodo hashcode predefinito nella classe Object presenta virtualmente sempre un numero univoco per ogni oggetto, anche se il equals()metodo viene sovrascritto in modo tale che due o più oggetti siano considerati uguali . Non importa quanto siano uguali gli oggetti se i loro hashcode non lo riflettono. Quindi ancora una volta: se due oggetti sono uguali, anche i loro hashcode devono essere uguali.


4
Esempio perfetto Dimostrò chiaramente la differenza!
coderpc,

3
bello spiegato @rajeev
VdeX

2
@VikasVerma è uguale a oggetto avrà codice hash uguale non significa che oggetto disuguale avrà codice hash disuguale. Cosa succede se gli oggetti sono effettivamente diversi, ma il loro hashcode è lo stesso?
Ravi,

1
spiegato molto bene :)
Rahul

4
risposta molto meglio della risposta accettata! Grazie
Driftwood il

50

Devi sovrascrivere hashCode () in ogni classe che sovrascrive equals (). In caso contrario, si verificherà una violazione del contratto generale per Object.hashCode (), che impedirà alla classe di funzionare correttamente insieme a tutte le raccolte basate su hash, inclusi HashMap, HashSet e Hashtable.


   da Effective Java , di Joshua Bloch

Definendo equals()e hashCode()coerentemente, è possibile migliorare l'usabilità delle classi come chiavi nelle raccolte basate su hash. Come spiega il documento API per hashCode: "Questo metodo è supportato a vantaggio di hashtable come quelli forniti da java.util.Hashtable."

La migliore risposta alla tua domanda su come implementare questi metodi in modo efficiente sta suggerendo di leggere il capitolo 3 di Java efficace .


4
Questa è la risposta corretta. A corollario, ovviamente, che se non usi mai la classe in una raccolta basata su hash, non importa che non hai implementato hashCode().
magro,

1
In casi più complessi, non sai mai se le raccolte che usi utilizzano hash, quindi stai lontano da "non importa che non hai implementato hashCode ()"
Victor Sergienko

1
Posso ignorare hashCode () senza sovrascrivere equals ()?
Johnny,

@StasS, sì, contrariamente a quanto dice la risposta accettata. Vedi la spiegazione nella seconda parte di questo articolo: Perché dovresti sempre sovrascrivere hashCode quando
esegui l'

22

In poche parole, il metodo uguale in Oggetto verifica l'uguaglianza di riferimento, in quanto due istanze della classe potrebbero essere semanticamente uguali quando le proprietà sono uguali. Questo è per esempio importante quando metti i tuoi oggetti in un contenitore che utilizza uguale e hashcode, come HashMap e Set . Diciamo che abbiamo una classe come:

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }
}

Creiamo due istanze con lo stesso ID :

Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");

Senza scavalcare uguali stiamo ottenendo:

  • a.equals (b) è falso perché sono due istanze diverse
  • a.equals (a) è vero poiché è la stessa istanza
  • b.equals (b) è vero poiché è la stessa istanza

Corretta? Beh, forse, se questo è quello che vuoi. Ma supponiamo che vogliamo che gli oggetti con lo stesso ID siano lo stesso oggetto, indipendentemente dal fatto che siano due istanze diverse. Sostituiamo equals (e hashcode):

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Foo) {
            return ((Foo)other).id.equals(this.id);   
        }
    }

    @Override
    public int hashCode() {
        return this.id.hashCode();
    }
}

Per quanto riguarda l'implementazione di uguali e hashcode, posso consigliare di utilizzare i metodi di supporto di Guava


20

L'identità non è uguaglianza.

  • è uguale ==all'identità del test dell'operatore .
  • equals(Object obj) Il metodo confronta il test di uguaglianza (cioè dobbiamo dire l'uguaglianza sovrascrivendo il metodo)

Perché devo sovrascrivere i metodi equals e hashCode in Java?

Innanzitutto dobbiamo capire l'uso del metodo uguale.

Per identificare le differenze tra due oggetti, abbiamo bisogno di sostituire il metodo uguale.

Per esempio:

Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.

------------------------------
Now I have overriden Customer class equals method as follows:
 @Override
    public boolean equals(Object obj) {
        if (this == obj)   // it checks references
            return true;
        if (obj == null) // checks null
            return false;
        if (getClass() != obj.getClass()) // both object are instances of same class or not
            return false;
        Customer other = (Customer) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference 
            return false;
        return true; 
    }
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2);  // returns true by our own logic

Ora il metodo hashCode può capire facilmente.

hashCode produce numeri interi per memorizzare oggetti in strutture dati come HashMap , HashSet .

Supponiamo di avere l'override del metodo uguale Customera sopra,

customer1.equals(customer2);  // returns true by our own logic

Mentre si lavora con la struttura dei dati quando si memorizzano oggetti in bucket (bucket è un nome di fantasia per cartella). Se utilizziamo la tecnica hash integrata, per due clienti superiori genera due hashcode diversi. Quindi stiamo conservando lo stesso identico oggetto in due luoghi diversi. Per evitare questo tipo di problemi dovremmo ignorare il metodo hashCode basato anche sui seguenti principi.

  • istanze disuguali possono avere lo stesso hashcode.
  • istanze uguali dovrebbero restituire lo stesso hashcode.

3
Questo è quello che cercavo dall'ultima ora. Fantastico compagno (y)
Adnan,

13

Ok, lasciami spiegare il concetto con parole molto semplici.

Innanzitutto da una prospettiva più ampia abbiamo raccolte e hashmap è una delle strutture di dati nelle raccolte.

Per capire perché dobbiamo sovrascrivere entrambi i metodi egual e hashcode, se è necessario prima capire cos'è hashmap e cosa fa.

Una hashmap è una struttura di dati che memorizza coppie di valori-chiave di dati in modo array. Diciamo a [], dove ogni elemento in 'a' è una coppia valore-chiave.

Inoltre, ogni indice nell'array sopra riportato può essere un elenco collegato in modo da avere più di un valore in un indice.

Ora perché viene utilizzata una hashmap? Se dobbiamo cercare tra un array di grandi dimensioni, quindi cercare in ciascuno di essi se non saranno efficienti, quindi quale tecnica di hash ci dice che consente di pre-elaborare l'array con una certa logica e raggruppare gli elementi in base a tale logica, ad esempio l'hashing

es: abbiamo array 1,2,3,4,5,6,7,8,9,10,11 e applichiamo una funzione hash mod 10, quindi 1,11 saranno raggruppati insieme. Quindi se dovessimo cercare 11 nell'array precedente, dovremmo iterare l'array completo ma quando lo raggruppiamo limitiamo il nostro ambito di iterazione migliorando così la velocità. Quella struttura di dati utilizzata per archiviare tutte le informazioni di cui sopra può essere pensata come un array 2d per semplicità

Ora, a parte l'hashmap di cui sopra, dice anche che non aggiungerà alcun duplicato in esso. E questo è il motivo principale per cui dobbiamo sovrascrivere gli uguali e l'hashcode

Quindi, quando si dice che spiegano il funzionamento interno dell'hashmap, dobbiamo trovare quali metodi ha l'hashmap e come segue le regole di cui sopra che ho spiegato sopra

quindi l'hashmap ha un metodo chiamato put (K, V) e secondo l'hashmap dovrebbe seguire le regole di cui sopra per distribuire in modo efficiente l'array e non aggiungere duplicati

quindi ciò che fa è che prima genererà l'hashcode per la chiave data per decidere in quale indice deve essere inserito il valore. Se nulla è presente in quell'indice, il nuovo valore verrà aggiunto laggiù, se qualcosa è già presente laggiù quindi il nuovo valore deve essere aggiunto dopo la fine dell'elenco collegato in quell'indice. ma ricorda che non devono essere aggiunti duplicati secondo il comportamento desiderato dell'hashmap. quindi supponiamo che tu abbia due oggetti Integer aa = 11, bb = 11. come ogni oggetto derivato dalla classe di oggetti, l'implementazione predefinita per il confronto di due oggetti è che confronta il riferimento e non i valori all'interno dell'oggetto. Quindi, nel caso precedente, sia se semanticamente uguali fallirà il test di uguaglianza, sia la possibilità che esistano due oggetti con lo stesso codice hash e gli stessi valori creando duplicati. Se eseguiamo l'override, potremmo evitare di aggiungere duplicati. Puoi anche fare riferimento aDettaglio di lavoro

import java.util.HashMap;


public class Employee {

String name;
String mobile;
public Employee(String name,String mobile) {
    this.name=name;
    this.mobile=mobile;
}

@Override
public int hashCode() {
    System.out.println("calling hascode method of Employee");
    String str=this.name;
    Integer sum=0;
    for(int i=0;i<str.length();i++){
        sum=sum+str.charAt(i);
    }
    return sum;

}
@Override
public boolean equals(Object obj) {
    // TODO Auto-generated method stub
    System.out.println("calling equals method of Employee");
    Employee emp=(Employee)obj;
    if(this.mobile.equalsIgnoreCase(emp.mobile)){

        System.out.println("returning true");
        return true;
    }else{
        System.out.println("returning false");
        return false;
    }


}

public static void main(String[] args) {
    // TODO Auto-generated method stub

    Employee emp=new Employee("abc", "hhh");
    Employee emp2=new Employee("abc", "hhh");
    HashMap<Employee, Employee> h=new HashMap<>();
    //for (int i=0;i<5;i++){
        h.put(emp, emp);
        h.put(emp2, emp2);

    //}

    System.out.println("----------------");
    System.out.println("size of hashmap: "+h.size());


}

}

Ho una confusione, perché dobbiamo scavalcare il metodo uguale quando sovrascriviamo il metodo hashCode in caso di HashMap? In ogni caso, hashmap sostituisce il valore se l'hashcode dell'oggetto è uguale.
Vikas Verma,

@VikasVerma hashmap non sostituisce alcun tipo di valore se l'hashcode degli oggetti è uguale, decide solo l'indice in cui deve essere posizionato l'oggetto appena aggiunto all'hashmap. Ora ci possono essere oggetti nell'indice, quindi per evitare duplicati sovrascriviamo il metodo equals e scriviamo la logica per definire quando i due oggetti in confronto devono essere considerati uguali. Se non sovrascritto, allora gli oggetti con gli stessi valori verranno archiviati perché il riferimento di entrambi gli oggetti sarà diverso
Chetan,

11

hashCode() :

Se si sovrascrive solo il metodo hash-code, non accadrà nulla. Perché restituisce sempre nuovo hashCodeper ogni oggetto come classe Object.

equals() :

Se si sovrascrive solo il metodo uguale, a.equals(b)è vero significa che hashCodedi aeb devono essere uguali ma non accadere. Perché non hai ignorato il hashCodemetodo.

Nota: il hashCode()metodo della classe Object restituisce sempre nuovo hashCodeper ogni oggetto.

Pertanto, quando è necessario utilizzare l'oggetto nella raccolta basata sull'hash, è necessario ignorare sia equals()e hashCode().


Questo è un punto interessante, sull'override solo hashCode () . Va tutto bene, vero? O possono esserci anche casi problematici?
Johnny,

1
Questa è una risposta fuorviante e sbagliata. L'override (= only =) hashCode () si assicura che ogni oggetto che viene istanziato della rispettiva classe con proprietà simili abbia lo stesso codice hash. Ma non sarà utile in quanto nessuno di loro sarà uguale l'uno all'altro.
mfaisalhyder,

8

Java pone una regola che

"Se due oggetti sono uguali usando il metodo uguale alla classe Oggetto, il metodo hashcode dovrebbe fornire lo stesso valore per questi due oggetti."

Quindi, se nella nostra classe abbiamo la precedenza equals(), dovremmo ignorare hashcode()anche il metodo per seguire questa regola. Entrambi i metodi equals()e hashcode(), Hashtablead esempio , vengono utilizzati per memorizzare i valori come coppie chiave-valore. Se sovrascriviamo l'uno e non l'altro, esiste la possibilità che Hashtablenon possa funzionare come vogliamo, se utilizziamo tale oggetto come chiave.


6

Perché se non li si sovrascriverà, verrà utilizzata l'implementazione predefinita in Object.

Dato che l'uguaglianza dell'istanza e i valori di hascode generalmente richiedono la conoscenza di ciò che costituisce un oggetto, in genere dovranno essere ridefiniti nella classe per avere un significato tangibile.


6

Per utilizzare i nostri oggetti di classe come chiavi in ​​raccolte come HashMap, Hashtable ecc., Dovremmo sovrascrivere entrambi i metodi (hashCode () e equals ()) avendo una consapevolezza del funzionamento interno della raccolta. Altrimenti, porta a risultati errati che non ci aspettiamo.


6

Aggiungendo alla risposta di @Lombo

Quando sarà necessario sovrascrivere equals ()?

L'implementazione predefinita di Object's equals () è

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

il che significa che due oggetti saranno considerati uguali solo se hanno lo stesso indirizzo di memoria, il che sarà vero solo se si confronta un oggetto con se stesso.

Ma potresti voler considerare due oggetti uguali se hanno lo stesso valore per una o più delle loro proprietà (fai riferimento all'esempio fornito nella risposta di @Lombo).

Quindi prevarrete equals()in queste situazioni e dareste le vostre condizioni per l'uguaglianza.

Ho implementato con successo equals () e sta funzionando alla grande, quindi perché stanno chiedendo di sovrascrivere anche hashCode ()?

Bene, purché non utilizzi raccolte basate su "Hash" sulla tua classe definita dall'utente, va bene. Ma un po 'di tempo in futuro potresti voler usare HashMapo HashSete se non lo fai overridee "implementi correttamente" hashCode () , queste raccolte basate su Hash non funzioneranno come previsto.

Sostituisci solo equivale (aggiunta alla risposta di @Lombo)

myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?

Prima di tutto, HashMap controlla se l'hashCode di secondè lo stesso di first. Solo se i valori sono gli stessi, procederà a verificare l'uguaglianza nello stesso bucket.

Ma qui hashCode è diverso per questi 2 oggetti (perché hanno un indirizzo di memoria diverso rispetto all'implementazione predefinita). Quindi non si preoccuperà nemmeno di verificare l'uguaglianza.

Se hai un punto di interruzione all'interno del tuo metodo overals (uguale a), non entrerebbe se avessero diversi hashCode. contains()controlla hashCode()e solo se sono uguali chiamerebbe il tuo equals()metodo.

Perché non possiamo fare in modo che HashMap controlli l'uguaglianza in tutti i bucket? Quindi non è necessario che io ignori hashCode () !!

Quindi ti manca il punto delle raccolte basate su hash. Considera quanto segue:

Your hashCode() implementation : intObject%9.

Di seguito sono riportate le chiavi memorizzate sotto forma di secchi.

Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...

Dì, vuoi sapere se la mappa contiene la chiave 10. Desideri cercare tutti i secchi? o Vorresti cercare un solo bucket?

Sulla base dell'hashCode, identificheresti che se è presente 10, deve essere presente nel Bucket 1. Quindi verrà cercato solo il Bucket 1 !!


5
class A {
    int i;
    // Hashing Algorithm
    if even number return 0 else return 1
    // Equals Algorithm,
    if i = this.i return true else false
}
  • put ('chiave', 'valore') calcolerà il valore di hash usando hashCode()per determinare il bucket e usa il equals()metodo per scoprire se il valore è già presente nel bucket. In caso contrario verrà aggiunto altrimenti verrà sostituito con il valore corrente
  • get ('chiave') utilizzerà hashCode()per trovare prima la voce (bucket) e equals()per trovare il valore in voce

se entrambi vengono ignorati,

Mappa < A >

Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...

se uguale a non viene ignorato

Mappa < A >

Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..

Se hashCode non viene sovrascritto

Mappa < A >

Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...

Contratto uguale per HashCode

  1. Due chiavi uguali secondo il metodo uguale dovrebbero generare lo stesso hashCode
  2. Due chiavi che generano lo stesso codice hash non devono essere uguali (nell'esempio precedente tutti i numeri pari generano lo stesso codice hash)

4

Considera la raccolta di palline in un secchio tutto di colore nero. Il tuo compito è colorare quelle palle come segue e usarle per il gioco appropriato,

Per il tennis - Giallo, rosso. Per Cricket - Bianco

Ora il secchio ha le palle in tre colori giallo, rosso e bianco. E che ora hai fatto la colorazione Solo tu sai quale colore è per quale gioco.

Colorare le palle - Hashing. Scegliere la palla per il gioco - Uguale.

Se hai fatto la colorazione e qualcuno sceglie la palla per il cricket o il tennis, non si preoccuperanno del colore !!!


4

Stavo esaminando la spiegazione "Se si esegue l'override di hashCode solo quando lo si chiama myMap.put(first,someValue)prende prima, calcola il suo hashCode e lo memorizza in un determinato bucket. Quindi, quando lo si chiama myMap.put(first,someOtherValue), dovrebbe sostituire prima con il secondo secondo la documentazione della mappa perché sono uguali (secondo la nostra definizione). " :

Penso che la seconda volta, quando stiamo aggiungendo myMap, dovrebbe essere il 'secondo' oggetto similemyMap.put(second,someOtherValue)


4

1) L'errore comune è mostrato nell'esempio seguente.

public class Car {

    private String color;

    public Car(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if(obj==null) return false;
        if (!(obj instanceof Car))
            return false;   
        if (obj == this)
            return true;
        return this.color.equals(((Car) obj).color);
    }

    public static void main(String[] args) {
        Car a1 = new Car("green");
        Car a2 = new Car("red");

        //hashMap stores Car type and its quantity
        HashMap<Car, Integer> m = new HashMap<Car, Integer>();
        m.put(a1, 10);
        m.put(a2, 20);
        System.out.println(m.get(new Car("green")));
    }
}

l'auto verde non è stata trovata

2. Problema causato da hashCode ()

Il problema è causato dal metodo non sovrascritto hashCode(). Il contratto tra equals()ed hashCode()è:

  1. Se due oggetti sono uguali, devono avere lo stesso codice hash.
  2. Se due oggetti hanno lo stesso codice hash, possono essere o meno uguali.

    public int hashCode(){  
      return this.color.hashCode(); 
    }

4

È utile quando si utilizzano oggetti valore . Quello che segue è un estratto dal Portland Pattern Repository :

Esempi di oggetti valore sono cose come numeri, date, denaro e stringhe. Di solito, sono piccoli oggetti che vengono utilizzati abbastanza ampiamente. La loro identità si basa sul loro stato piuttosto che sulla loro identità di oggetto. In questo modo, è possibile avere più copie dello stesso oggetto valore concettuale.

Quindi posso avere più copie di un oggetto che rappresenta la data del 16 gennaio 1998. Tutte queste copie saranno uguali tra loro. Per un oggetto piccolo come questo, è spesso più facile crearne di nuovi e spostarli piuttosto che fare affidamento su un singolo oggetto per rappresentare la data.

Un oggetto valore deve sempre sovrascrivere .equals () in Java (o = in Smalltalk). (Ricorda di sovrascrivere anche .hashCode ().)


3

Supponiamo di avere una classe (A) che aggrega altre due (B) (C) e che è necessario memorizzare istanze di (A) all'interno della tabella hash. L'implementazione predefinita consente solo di distinguere le istanze, ma non per (B) e (C). Quindi due istanze di A potrebbero essere uguali, ma il default non ti permetterebbe di confrontarle in modo corretto.


3

I metodi uguali e hashcode sono definiti nella classe di oggetti. Per impostazione predefinita se il metodo equals restituisce true, il sistema andrà oltre e verificherà il valore del codice hash. Se anche il codice hash dei 2 oggetti è lo stesso, gli oggetti saranno considerati uguali. Pertanto, se si sovrascrive solo il metodo equals, anche se il metodo egual override indica che 2 oggetti sono uguali, l'hashcode definito dal sistema potrebbe non indicare che i 2 oggetti sono uguali. Quindi dobbiamo anche sovrascrivere il codice hash.


Se il metodo equals restituisce true, non è necessario controllare l'hashcode. Se due oggetti hanno hashcode diversi, tuttavia, si dovrebbe essere in grado di considerarli diversi senza dover chiamare uguale. Inoltre, la consapevolezza che nessuna delle cose in una lista ha un particolare codice hash implica che nessuna delle cose nella lista può corrispondere all'oggetto nay con quel codice hash. Come semplice esempio, se si ha un elenco di oggetti i cui codici hash sono numeri pari e un elenco di oggetti in cui sono numeri dispari, nessun oggetto il cui codice hash è un numero pari sarà nel secondo elenco.
supercat,

Se uno aveva due oggetti X e Y i cui metodi "uguali" indicavano che corrispondevano, ma il codice hash di X era un numero pari e il codice hash di Y era un numero dispari, una raccolta come descritto sopra che faceva notare che il codice hash di Y oggetto era dispari e memorizzato nel secondo elenco non sarebbe in grado di trovare una corrispondenza per l'oggetto X. Osserverebbe che il codice hash di X era pari e, poiché il secondo elenco non ha oggetti con codici hash pari, non si preoccuperebbe per cercare lì qualcosa che corrisponda a X, anche se Y corrisponderebbe a X. Cosa dovresti dire ...
supercat

... sarebbe che molte raccolte eviteranno di confrontare cose i cui codici hash implicherebbero che non possono essere uguali. Dati due oggetti i cui codici hash sono sconosciuti, spesso è più veloce confrontarli direttamente che calcolare i loro codici hash, quindi non c'è garanzia che le cose che riportano codici hash diseguali ma che ritornano trueper equalsnon saranno considerate corrispondenti. D'altra parte, se le raccolte accadono notano che le cose non possono avere lo stesso codice hash, è probabile che non notino che sono uguali.
supercat,

3

Metodi uguali e Hashcode in Java

Sono metodi della classe java.lang.Object che è la super classe di tutte le classi (anche le classi personalizzate e altre definite nell'API java).

Implementazione:

public boolean equals (Oggetto obj)

public int hashCode ()

inserisci qui la descrizione dell'immagine

public boolean equals (Oggetto obj)

Questo metodo controlla semplicemente se due riferimenti a oggetti x e y si riferiscono allo stesso oggetto. cioè Controlla se x == y.

È riflessivo: per qualsiasi valore di riferimento x, x.equals (x) dovrebbe restituire true.

È simmetrico: per qualsiasi valore di riferimento x e y, x.equals (y) dovrebbe restituire true se e solo se y.equals (x) restituisce true.

È transitivo: per qualsiasi valore di riferimento x, ye z, se x.equals (y) restituisce true e y.equals (z) restituisce true, allora x.equals (z) dovrebbe restituire true.

È coerente: per qualsiasi valore di riferimento xey, invocazioni multiple di x.equals (y) restituiscono costantemente true o restituiscono costantemente false, a condizione che non vengano modificate informazioni utilizzate in confronti uguali sull'oggetto.

Per qualsiasi valore di riferimento non nullo x, x.equals (null) dovrebbe restituire false.

public int hashCode ()

Questo metodo restituisce il valore del codice hash per l'oggetto su cui viene invocato questo metodo. Questo metodo restituisce il valore del codice hash come numero intero ed è supportato a vantaggio delle classi di raccolta basate sull'hash come Hashtable, HashMap, HashSet ecc. Questo metodo deve essere sovrascritto in ogni classe che sostituisce il metodo equals.

Il contratto generale di hashCode è:

Ogni volta che viene invocato sullo stesso oggetto più di una volta durante un'esecuzione di un'applicazione Java, il metodo hashCode deve restituire costantemente lo stesso numero intero, a condizione che non vengano modificate informazioni utilizzate in confronti uguali sull'oggetto.

Questo numero intero non deve rimanere coerente da un'esecuzione di un'applicazione a un'altra esecuzione della stessa applicazione.

Se due oggetti sono uguali in base al metodo equals (Object), la chiamata del metodo hashCode su ciascuno dei due oggetti deve produrre lo stesso risultato intero.

Non è necessario che se due oggetti non sono uguali in base al metodo equals (java.lang.Object), la chiamata del metodo hashCode su ciascuno dei due oggetti deve produrre risultati interi distinti. Tuttavia, il programmatore deve essere consapevole che la produzione di risultati interi distinti per oggetti disuguali può migliorare le prestazioni degli hashtabili.

Gli oggetti uguali devono produrre lo stesso codice hash purché siano uguali, tuttavia gli oggetti disuguali non devono produrre codici hash distinti.

risorse:

JavaRanch

Immagine


L'immagine (collegamento video) è in modalità privata. Renderlo pubblico da guardare.
UdayKiran Pulipati,

2

Nell'esempio seguente, se si annota l'override per uguale o hashcode nella classe Person, questo codice non riuscirà a cercare l'ordine di Tom. L'uso dell'implementazione predefinita di hashcode può causare errori nelle ricerche hashtable.

Quello che ho sotto è un codice semplificato che tira su l'ordine delle persone per persona. La persona viene utilizzata come chiave nell'hashtable.

public class Person {
    String name;
    int age;
    String socialSecurityNumber;

    public Person(String name, int age, String socialSecurityNumber) {
        this.name = name;
        this.age = age;
        this.socialSecurityNumber = socialSecurityNumber;
    }

    @Override
    public boolean equals(Object p) {
        //Person is same if social security number is same

        if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
            return true;
        } else {
            return false;
        }

    }

    @Override
    public int hashCode() {        //I am using a hashing function in String.java instead of writing my own.
        return socialSecurityNumber.hashCode();
    }
}


public class Order {
    String[]  items;

    public void insertOrder(String[]  items)
    {
        this.items=items;
    }

}



import java.util.Hashtable;

public class Main {

    public static void main(String[] args) {

       Person p1=new Person("Tom",32,"548-56-4412");
        Person p2=new Person("Jerry",60,"456-74-4125");
        Person p3=new Person("Sherry",38,"418-55-1235");

        Order order1=new Order();
        order1.insertOrder(new String[]{"mouse","car charger"});

        Order order2=new Order();
        order2.insertOrder(new String[]{"Multi vitamin"});

        Order order3=new Order();
        order3.insertOrder(new String[]{"handbag", "iPod"});

        Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
        hashtable.put(p1,order1);
        hashtable.put(p2,order2);
        hashtable.put(p3,order3);

       //The line below will fail if Person class does not override hashCode()
       Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
        for(String item:tomOrder.items)
        {
            System.out.println(item);
        }
    }
}

2

Le classi stringa e wrapper hanno un'implementazione diversa di equals()ehashCode() metodi diversi rispetto alla classe Object. Il metodo equals () della classe Object confronta i riferimenti degli oggetti, non i contenuti. Il metodo hashCode () della classe Object restituisce hashcode distinto per ogni singolo oggetto indipendentemente dal fatto che il contenuto sia lo stesso.

Porta problema quando si utilizza la raccolta di mappe e la chiave è di tipo persistente, tipo StringBuffer / builder. Poiché non sovrascrivono equals () e hashCode () a differenza della classe String, equals () restituirà false quando si confrontano due oggetti diversi anche se entrambi hanno lo stesso contenuto. Farà in modo che hashMap memorizzi le stesse chiavi di contenuto. Memorizzare le stesse chiavi di contenuto significa che sta violando la regola di Map perché Map non consente affatto chiavi duplicate. Pertanto sovrascrivi i metodi equals () e hashCode () nella tua classe e fornisci l'implementazione (IDE può generare questi metodi) in modo che funzionino come String's equals () e hashCode () e impediscano le stesse chiavi di contenuto.

Devi sovrascrivere il metodo hashCode () insieme a equals () perché equals () funziona secondo hashcode.

Inoltre l'override del metodo hashCode () insieme a equals () aiuta a mantenere intatto il contratto equals () - hashCode (): "Se due oggetti sono uguali, devono avere lo stesso codice hash".

Quando è necessario scrivere un'implementazione personalizzata per hashCode ()?

Come sappiamo, il funzionamento interno di HashMap è in linea di principio di Hashing. Esistono alcuni bucket in cui vengono archiviati i entryset. Personalizza l'implementazione di hashCode () in base alle tue esigenze in modo che gli oggetti della stessa categoria possano essere memorizzati nello stesso indice. quando si memorizzano i valori nella raccolta Mappa usando il put(k,v)metodo, l'implementazione interna di put () è:

put(k, v){
hash(k);
index=hash & (n-1);
}

Significa, genera indice e l'indice viene generato in base all'hashcode di un particolare oggetto chiave. Quindi fai in modo che questo metodo generi hashcode in base alle tue esigenze perché gli stessi entryset di hashcode verranno archiviati nello stesso bucket o indice.

Questo è tutto!


1

hashCode()Il metodo viene utilizzato per ottenere un numero intero univoco per un determinato oggetto. Questo numero intero viene utilizzato per determinare la posizione del bucket, quando questo oggetto deve essere archiviato in alcuni HashTable, HashMapcome la struttura dei dati. Per impostazione predefinita, il hashCode()metodo Object restituisce e la rappresentazione intera dell'indirizzo di memoria in cui è archiviato l'oggetto.

Il hashCode()metodo degli oggetti viene utilizzato quando li inseriamo in un HashTable, HashMapo HashSet. Maggiori informazioni HashTablessu Wikipedia.org come riferimento.

Per inserire qualsiasi voce nella struttura dei dati della mappa, abbiamo bisogno sia della chiave che del valore. Se sia la chiave che i valori sono tipi di dati definiti dall'utente, hashCode()la chiave verrà determinata dove archiviare l'oggetto internamente. Quando è necessario cercare anche l'oggetto dalla mappa, il codice hash della chiave determinerà dove cercare l'oggetto.

Il codice hash punta solo verso una determinata "area" (o elenco, bucket ecc.) Internamente. Poiché diversi oggetti chiave potrebbero potenzialmente avere lo stesso codice hash, il codice hash stesso non garantisce che venga trovata la chiave giusta. L' HashTableiterazione quindi di quest'area (tutte le chiavi con lo stesso codice hash) e utilizza il equals()metodo della chiave per trovare la chiave giusta. Una volta trovata la chiave giusta, viene restituito l'oggetto memorizzato per quella chiave.

Quindi, come possiamo vedere, una combinazione dei metodi hashCode()e equals()viene utilizzata per la memorizzazione e la ricerca di oggetti in a HashTable.

APPUNTI:

  1. Usa sempre gli stessi attributi di un oggetto per generare hashCode()ed equals()entrambi. Come nel nostro caso, abbiamo usato l'ID dipendente.

  2. equals() deve essere coerente (se gli oggetti non vengono modificati, deve continuare a restituire lo stesso valore).

  3. Ogni volta a.equals(b), quindi a.hashCode()deve essere uguale a b.hashCode().

  4. Se si esegue l'override di uno, è necessario ignorare l'altro.

http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html


hashCode()non viene utilizzato per restituire un numero intero univoco per ogni oggetto. Questo è impossibile. L'hai contraddetto tu stesso nella seconda frase del quarto paragrafo.
Marchese di Lorne,

@EJP, il più delle volte hascode () restituirà l'interger univoco per due oggetti diversi. Ma ci saranno possibilità di scontrarsi con hascode per due diversi oggetti, questo concetto è chiamato Hashcode Collision . Si prega di fare riferimento a: tech.queryhome.com/96931/…
Paramesh Korrakuti,

1

IMHO, è come dice la regola: se due oggetti sono uguali allora dovrebbero avere lo stesso hash, cioè oggetti uguali dovrebbero produrre valori di hash uguali.

Dato sopra, default equals () in Object è == che fa il confronto sull'indirizzo, hashCode () restituisce l'indirizzo in numero intero (hash sull'indirizzo effettivo) che è di nuovo distinto per Oggetto distinto.

Se è necessario utilizzare gli oggetti personalizzati nelle raccolte basate su Hash, è necessario sovrascrivere sia equals () che hashCode (), esempio Se si desidera mantenere l'HashSet degli oggetti Employee, se non uso hashCode più forte ed è uguale a Potrei finire con l'override dei due diversi oggetti Employee, questo succede quando uso l'età come hashCode (), tuttavia dovrei usare il valore univoco che può essere l'ID Employee.


1

Per aiutarti a verificare la presenza di oggetti duplicati, abbiamo bisogno di un uguale a uguale e hashCode.

Poiché hashcode restituisce sempre un numero, è sempre veloce recuperare un oggetto utilizzando un numero anziché una chiave alfabetica. Come farà? Supponiamo di aver creato un nuovo oggetto passando un valore che è già disponibile in qualche altro oggetto. Ora il nuovo oggetto restituirà lo stesso valore hash di un altro oggetto perché il valore passato è lo stesso. Una volta restituito lo stesso valore hash, JVM andrà sempre allo stesso indirizzo di memoria e se nel caso in cui siano presenti più oggetti per lo stesso valore hash, utilizzerà il metodo equals () per identificare l'oggetto corretto.


1

Quando si desidera archiviare e recuperare l'oggetto personalizzato come chiave in Mappa, è necessario sovrascrivere sempre uguale e hashCode nell'oggetto personalizzato. Per esempio:

Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");

Qui p1 & p2 considereranno come un solo oggetto e le mapdimensioni saranno solo 1 perché sono uguali.


1
public class Employee {

    private int empId;
    private String empName;

    public Employee(int empId, String empName) {
        super();
        this.empId = empId;
        this.empName = empName;
    }

    public int getEmpId() {
        return empId;
    }

    public void setEmpId(int empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + "]";
    }

    @Override
    public int hashCode() {
        return empId + empName.hashCode();
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (!(this instanceof Employee)) {
            return false;
        }
        Employee emp = (Employee) obj;
        return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
    }

}

Classe di prova

public class Test {

    public static void main(String[] args) {
        Employee emp1 = new Employee(101,"Manash");
        Employee emp2 = new Employee(101,"Manash");
        Employee emp3 = new Employee(103,"Ranjan");
        System.out.println(emp1.hashCode());
        System.out.println(emp2.hashCode());
        System.out.println(emp1.equals(emp2));
        System.out.println(emp1.equals(emp3));
    }

}

In Object Class equals (Object obj) viene utilizzato per confrontare la comparazione degli indirizzi, ecco perché nella classe Test se si confrontano due oggetti, allora si equivale al metodo che dà false, ma quando si sovrascrive hashcode () è possibile confrontare il contenuto e fornire il risultato corretto.


e classe di test che ho aggiunto nel programma seguente.
Manash Ranjan Dakua,

In Object Class equals (Object obj) viene utilizzato per confrontare la comparazione degli indirizzi, ecco perché nella classe Test se si confrontano due oggetti, allora si equivale al metodo che dà false, ma quando si sovrascrive hashcode () è possibile confrontare il contenuto e fornire il risultato corretto.
Manash Ranjan Dakua,

1
puoi usare il link modifica appena sotto questa risposta per aggiungere alla tua risposta .. Per favore non aggiungere una risposta come due incomplete
Suraj Rao,

1

Se esegui l'override equals()e non hashcode(), non troverai alcun problema a meno che tu o qualcun altro usi quel tipo di classe in una raccolta con hash come HashSet. Le persone prima di me hanno spiegato chiaramente la teoria documentata più volte, sono qui solo per fornire un esempio molto semplice.

Considera una classe il cui equals()bisogno di significare qualcosa di personalizzato: -

    public class Rishav {

        private String rshv;

        public Rishav(String rshv) {
            this.rshv = rshv;
        }

        /**
        * @return the rshv
        */
        public String getRshv() {
            return rshv;
        }

        /**
        * @param rshv the rshv to set
        */
        public void setRshv(String rshv) {
            this.rshv = rshv;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Rishav) {
                obj = (Rishav) obj;
                if (this.rshv.equals(((Rishav) obj).getRshv())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return rshv.hashCode();
        }

    }

Ora considera questa classe principale: -

    import java.util.HashSet;
    import java.util.Set;

    public class TestRishav {

        public static void main(String[] args) {
            Rishav rA = new Rishav("rishav");
            Rishav rB = new Rishav("rishav");
            System.out.println(rA.equals(rB));
            System.out.println("-----------------------------------");

            Set<Rishav> hashed = new HashSet<>();
            hashed.add(rA);
            System.out.println(hashed.contains(rB));
            System.out.println("-----------------------------------");

            hashed.add(rB);
            System.out.println(hashed.size());
        }

    }

Ciò produrrà il seguente output: -

    true
    -----------------------------------
    true
    -----------------------------------
    1

Sono felice dei risultati. Ma se non ho scavalcato hashCode(), causerà un incubo poiché gli oggetti Rishavcon lo stesso contenuto membro non saranno più trattati come unici in quanto hashCodediversi, generati dal comportamento predefinito, ecco l'output: -

    true
    -----------------------------------
    false
    -----------------------------------
    2

0

Entrambi i metodi sono definiti nella classe Object. Ed entrambi sono nella sua implementazione più semplice. Quindi, quando ti serve, vuoi aggiungere un po 'più di implementazione a questi metodi, quindi hai la precedenza nella tua classe.

Per Ex: il metodo equals () nell'oggetto controlla solo la sua uguaglianza sul riferimento. Quindi, se è necessario confrontare anche il suo stato, è possibile sovrascriverlo come avviene nella classe String.


-3

Bah - "Devi sovrascrivere hashCode () in ogni classe che sovrascrive equals ()."

[da Effective Java, di Joshua Bloch?]

Non è questo il modo sbagliato? Sovrascrivere hashCode probabilmente implica che stai scrivendo una classe di chiave hash, ma l'override uguale sicuramente no. Esistono molte classi che non vengono utilizzate come chiavi hash, ma che desiderano un metodo di verifica dell'uguaglianza logica per qualche altro motivo. Se scegli "uguale a" per esso, potresti essere incaricato di scrivere un'implementazione di hashCode mediante un'applicazione troppo zelante di questa regola. Tutto ciò che ottiene è l'aggiunta di codice non testato nella base di codice, un male che aspetta di far inciampare qualcuno in futuro. Anche scrivere il codice che non ti serve è anti-agile. È solo sbagliato (e un ide generato uno sarà probabilmente incompatibile con i tuoi eguali fatti a mano).

Sicuramente avrebbero dovuto imporre un'interfaccia su oggetti scritti per essere usati come chiavi? Indipendentemente da ciò, Object non avrebbe mai dovuto fornire hashCode () predefinito e equals () imho. Probabilmente ha incoraggiato molte raccolte di hash spezzate.

Ma comunque, penso che la "regola" sia scritta in primo piano. Nel frattempo, continuerò a evitare di usare "equals" per i metodi di test di uguaglianza :-(

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.