Entrare nella zona grigia di "on / off topic", ma è necessario eliminare la confusione riguardo al suggerimento di Oscar Reyes che più collisioni di hash siano una buona cosa perché riduce il numero di elementi nella HashMap. Potrei fraintendere quello che sta dicendo Oscar, ma non mi sembra di essere l'unico: kdgregory, delfuego, Nash0, e sembra che condividiamo tutti la stessa (mis) comprensione.
Se capisco cosa sta dicendo Oscar sulla stessa classe con lo stesso codice hash, sta proponendo che solo un'istanza di una classe con un dato codice hash verrà inserita in HashMap. Ad esempio, se ho un'istanza di SomeClass con un hashcode di 1 e una seconda istanza di SomeClass con un hashcode di 1, viene inserita solo un'istanza di SomeClass.
L'esempio di Java Pastebin su http://pastebin.com/f20af40b9 sembra indicare che quanto sopra riassume correttamente ciò che Oscar propone.
Indipendentemente da qualsiasi comprensione o malinteso, ciò che accade è che istanze diverse della stessa classe non vengono inserite una sola volta in HashMap se hanno lo stesso codice hash, non finché non viene determinato se le chiavi sono uguali o meno. Il contratto hashcode richiede che oggetti uguali abbiano lo stesso hashcode; tuttavia, non richiede che oggetti disuguali abbiano codici hash diversi (sebbene ciò possa essere desiderabile per altri motivi) [1].
Segue l'esempio pastebin.com/f20af40b9 (a cui Oscar si riferisce almeno due volte), ma leggermente modificato per utilizzare le asserzioni JUnit piuttosto che le righe di stampa. Questo esempio viene utilizzato per supportare la proposta che gli stessi codici hash causino collisioni e quando le classi sono le stesse viene creata solo una voce (ad esempio, solo una stringa in questo caso specifico):
@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
String s = new String("ese");
String ese = new String("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// AND equal
assertTrue(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(2, map.size());
assertEquals(2, map.get("ese"));
assertEquals(3, map.get(some));
assertTrue(s.equals(ese) && s.equals("ese"));
}
class SomeClass {
public int hashCode() {
return 100727;
}
}
Tuttavia, il codice hash non è la storia completa. Ciò che l'esempio di pastebin trascura è il fatto che entrambi s
e ese
sono uguali: sono entrambi la stringa "ese". Pertanto, l'inserimento o il recupero del contenuto della mappa utilizzando s
o ese
o "ese"
come chiave sono tutti equivalenti perché s.equals(ese) && s.equals("ese")
.
Un secondo test dimostra che è errato concludere che codici hash identici sulla stessa classe sono la ragione per cui il valore chiave -> s -> 1
viene sovrascritto da ese -> 2
quando map.put(ese, 2)
viene chiamato nel test uno. Nella seconda prova s
e ese
hanno ancora lo stesso codice hash (verificato da assertEquals(s.hashCode(), ese.hashCode());
) E sono la stessa classe. Tuttavia, s
e ese
sono MyString
istanze in questo test, non String
istanze Java - con l'unica differenza rilevante per questo test che è uguale: String s equals String ese
nel test uno sopra, mentre MyStrings s does not equal MyString ese
nel test due:
@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
MyString s = new MyString("ese");
MyString ese = new MyString("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// BUT not equal
assertFalse(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(3, map.size());
assertEquals(1, map.get(s));
assertEquals(2, map.get(ese));
assertEquals(3, map.get(some));
}
/**
* NOTE: equals is not overridden so the default implementation is used
* which means objects are only equal if they're the same instance, whereas
* the actual Java String class compares the value of its contents.
*/
class MyString {
String i;
MyString(String i) {
this.i = i;
}
@Override
public int hashCode() {
return 100727;
}
}
Sulla base di un commento successivo, Oscar sembra invertire ciò che ha detto prima e riconosce l'importanza della parità. Tuttavia, sembra ancora che ciò che conta è la nozione di uguale, non la "stessa classe", non è chiara (enfasi mia):
"Non proprio. L'elenco viene creato solo se l'hash è lo stesso, ma la chiave è diversa. Ad esempio, se una stringa fornisce il codice hash 2345 e e Integer fornisce lo stesso codice hash 2345, il numero intero viene inserito nell'elenco perché String. equals (Integer) è falso. Ma se hai la stessa classe (o almeno .equals restituisce true), viene utilizzata la stessa voce. Ad esempio new String ("one") e `new String (" one ") usati come keys, utilizzerà la stessa voce. In realtà questo è il punto INTERO di HashMap in primo luogo! Guarda tu stesso: pastebin.com/f20af40b9 - Oscar Reyes "
rispetto ai commenti precedenti che affrontano esplicitamente l'importanza di una classe identica e dello stesso codice hash, senza menzione di uguali:
"@delfuego: guarda tu stesso: pastebin.com/f20af40b9 Quindi, in questa domanda viene utilizzata la stessa classe (aspetta un minuto, viene utilizzata la stessa classe, giusto?) Il che implica che quando viene utilizzato lo stesso hash viene utilizzata la stessa voce viene utilizzato e non c'è "lista" di voci. - Oscar Reyes "
o
"In realtà questo aumenterebbe le prestazioni. Più collisioni eq meno voci nell'equalizzazione della tabella hash. Meno lavoro da fare. Non è l'hash (che sembra a posto) né la tabella hash (che funziona alla grande) Scommetto che è sull'oggetto creazione in cui la performance è degradante. - Oscar Reyes "
o
"@kdgregory: Sì, ma solo se la collisione avviene con classi diverse, per la stessa classe (che è il caso) viene utilizzata la stessa voce. - Oscar Reyes"
Di nuovo, potrei fraintendere ciò che Oscar stava effettivamente cercando di dire. Tuttavia, i suoi commenti originali hanno causato tanta confusione che sembra prudente chiarire tutto con alcuni test espliciti, quindi non ci sono dubbi persistenti.
[1] - Da Effective Java, seconda edizione di Joshua Bloch:
Ogni volta che viene invocato sullo stesso oggetto più di una volta durante l'esecuzione di un'applicazione, il metodo hashCode deve restituire costantemente lo stesso numero intero, a condizione che non venga modificata alcuna informazione utilizzata 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 secondo il metodo equal s (Obj ect), la chiamata al metodo hashCode su ciascuno dei due oggetti deve produrre lo stesso risultato intero.
Non è necessario che se due oggetti sono disuguali secondo il metodo equal s (Object), la chiamata del metodo hashCode su ciascuno dei due oggetti deve produrre risultati interi distinti. Tuttavia, il programmatore deve essere consapevole del fatto che la produzione di risultati interi distinti per oggetti diversi può migliorare le prestazioni delle tabelle hash.