Prawdziwa odpowiedź na
dlaczego istnieje Comparator
interfejs, ale nie Hasher
i Equator
?
cytat dzięki uprzejmości Josha Blocha :
Oryginalne interfejsy API Java zostały wykonane bardzo szybko w krótkim terminie, aby sprostać zamykającemu się rynkowi. Oryginalny zespół Java wykonał niesamowitą robotę, ale nie wszystkie interfejsy API są idealne.
Problem leży wyłącznie w historii Javy, podobnie jak w innych podobnych sprawach, np . .clone()
Vs.Cloneable
tl; dr
dzieje się tak głównie z przyczyn historycznych; Obecne zachowanie / abstrakcja zostało wprowadzone w JDK 1.0 i nie zostało później naprawione, ponieważ było to praktycznie niemożliwe z zachowaniem kompatybilności kodu wstecznego.
Najpierw podsumujmy kilka dobrze znanych faktów o Javie:
- Java od samego początku do dnia dzisiejszego była dumnie kompatybilna wstecz, wymagając, aby starsze interfejsy API były nadal obsługiwane w nowszych wersjach,
- dlatego prawie każdy konstrukt językowy wprowadzony w JDK 1.0 przetrwał do dnia dzisiejszego,
Hashtable
, .hashCode()
i .equals()
zostały zaimplementowane w JDK 1.0, ( Hashtable )
Comparable
/ Comparator
został wprowadzony w JDK 1.2 ( porównywalny ),
Teraz wygląda to następująco:
- praktycznie niemożliwe i bezsensowne było modernizowanie
.hashCode()
i .equals()
wyróżnianie interfejsów przy jednoczesnym zachowaniu kompatybilności wstecznej po tym, jak ludzie zdali sobie sprawę, że istnieją lepsze abstrakcje niż umieszczanie ich w superobjektach, ponieważ np. każdy programista Java w wersji 1.2 wiedział, że każdy Object
je ma, i mieli pozostanie tam fizycznie w celu zapewnienia kompatybilności skompilowanego kodu (JVM) - i dodanie jawnego interfejsu do każdej Object
podklasy, która naprawdę je zaimplementowała, sprawiłoby, że ten bałagan byłby równy (sic!) do Clonable
jednego ( Bloch omawia, dlaczego klonowanie jest do bani , również omówione np. w EJ 2nd i wiele innych miejsc, w tym SO),
- po prostu zostawili je tam, aby przyszłe pokolenie miało stałe źródło WTF.
Teraz możesz zapytać „co Hashtable
ma z tym wszystkim”?
Odpowiedź brzmi: hashCode()
/ equals()
kontrakt i niezbyt dobre umiejętności projektowania języka przez głównych programistów Java w latach 1995/1996.
Cytat ze specyfikacji języka Java 1.0, z 1996 r. - 4.3.2 The Class Object
, str. 41:
Metody equals
i hashCode
zostały zadeklarowane na korzyść java.util.Hashtable
tablic mieszających, takich jak (§21.7). Metoda równa się definiuje pojęcie równości obiektu, które opiera się na porównaniu wartości, a nie odniesienia.
(uwaga dokładny ta wypowiedź została zmieniona w późniejszych wersjach, aby powiedzieć, cytat: The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, uniemożliwiając, aby bezpośredni Hashtable
- hashCode
- equals
połączenia bez czytania JLS historycznych!)
Zespół Java zdecydował, że chce dobrej kolekcji w stylu słownika, i stworzył Hashtable
(jak dotąd dobry pomysł), ale chciał, aby programista mógł go używać przy możliwie najmniejszej krzywej kodu / uczenia się (oops! Problemy z nadchodzącym!) - a ponieważ nie było jeszcze żadnych generycznych [to w końcu JDK 1.0], oznaczałoby to, że każdy Object
włożony Hashtable
musiałby jawnie zaimplementować jakiś interfejs (a interfejsy były wtedy jeszcze w początkowej fazie ... Comparable
nawet jeszcze!) , co zniechęca do korzystania z niego przez wiele osób - lub Object
musiałoby niejawnie zaimplementować jakąś metodę haszującą.
Oczywiście wybrali rozwiązanie 2 z powodów przedstawionych powyżej. Tak, teraz wiemy, że się mylili. ... z perspektywy czasu łatwo być mądrym. chichot
Teraz hashCode()
wymaga, aby każdy obiekt, który go posiada, musiał mieć odrębną equals()
metodę - więc było całkiem oczywiste, że equals()
trzeba go również wprowadzić Object
.
Ponieważ domyślne implementacje tych metod na ważność a
i b
Object
s są w zasadzie bezużyteczne, będąc zbędny (co a.equals(b)
równa się a==b
i a.hashCode() == b.hashCode()
mniej więcej równa się a==b
również, chyba że hashCode
i / lub equals
jest nadpisane, albo GC setki tysięcy Object
s podczas całego cyklu życia aplikacji 1 ) , można bezpiecznie powiedzieć, że zostały one dostarczone głównie jako środek zapasowy i dla wygody użytkowania. Właśnie w ten sposób dochodzimy do dobrze znanego faktu, który zawsze zastępuje oba .equals()
i .hashCode()
jeśli zamierzasz faktycznie porównywać obiekty lub przechowywać je w postaci skrótów. Zastąpienie tylko jednego z nich bez drugiego jest dobrym sposobem na wkręcenie kodu (przez nikczemne porównanie wyników lub niesamowicie wysokie wartości kolizji z wiadrem) - a obejście go jest źródłem ciągłego zamieszania i błędów dla początkujących (wyszukaj SO, aby zobaczyć to dla siebie) i ciągłe uciążliwości dla bardziej doświadczonych.
Zauważ też, że chociaż C # radzi sobie z equals i hashcode w nieco lepszy sposób, sam Eric Lippert twierdzi, że popełnił prawie taki sam błąd z C #, jaki Sun zrobił z Javą na wiele lat przed początkiem C # :
Ale dlaczego miałoby tak być, że każdy obiekt powinien mieć możliwość mieszania się w celu wstawienia do tablicy skrótów? Wydaje się dziwne, że każdy obiekt musi być w stanie to zrobić. Myślę, że gdybyśmy dzisiaj przeprojektowywali system typów od nowa, mieszanie mogłoby być wykonane inaczej, być może z IHashable
interfejsem. Ale kiedy zaprojektowano system typu CLR, nie istniały żadne typy ogólne, dlatego też tablica mieszająca ogólnego przeznaczenia musiała być w stanie przechowywać dowolny obiekt.
1 oczywiście Object#hashCode
nadal może się kolidować, ale zajmuje to trochę wysiłku, patrz: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 i powiązane raporty błędów, aby uzyskać szczegółowe informacje; /programming/1381060/hashcode-uniqueness/1381114#1381114 obejmuje ten temat bardziej szczegółowo.
Person
tej implementacji oczekiwanegoequals
ihashCode
zachowania. Miałbyś wtedyHashMap<PersonWrapper, V>
. Jest to przykład, w którym podejście oparte na czystym OOP nie jest eleganckie: nie każda operacja na obiekcie ma sens jako metoda tego obiektu. Cała JavaObject
typ jest amalgamatem różnych zadań - tylkogetClass
,finalize
atoString
metody wydają się uzasadnione zdalnie przez dzisiejszych najlepszych praktyk.