Java HR: metody equals() i hashCode()

Java HR: metody equals() i hashCode()

Jak wiadomo, każdy obiekt w Javie dziedziczy po klasie Object.

Klasa Object implementuje domyślnie metody:
  • clone()
  • equals(Object obj)
  • finalize()
  • getClass()
  • hashCode()
  • notify()
  • notifyAll()
  • toString()
  • wait()
  • wait(long timeout)
  • wait(long timeout, int nanos)

Do porównania dwóch obiektów potrzebujemy metod equalshashCode, które powinniśmy w takiej sytuacji nadpisać we własnej klasie.
Metody te są praktycznie nierozłączne, dlatego że wiąże je pewien kontrakt. Dlatego implementujemy albo obie, albo żadną.

Porównywanie obiektów


Operatory ==!= służą przede wszystkim do porównywania ze sobą typów prostych (prymitywów)

Przykład:

Jeśli użyjemy tego operatora do obiektów, porównamy ze sobą ich fizyczny adres w pamięci (referencję). W ten sposób możemy sprawdzić, czy to jest dokładnie ta sama instancja obiektu.

Przykład:

Należy pamiętać o podchwytliwym działaniu Stringów i faktu istnienia String Pool, przykład:

Ale jednocześnie:

Equals


Dla obiektów, z pomocą przychodzi equals(), który zwraca wartość boolean czy obiekty są jednakowe (niekoniecznie będąc tą samą instancją) czy też nie.

Przykład:

Oczywiście trzeba wtedy pamiętać o własnej, poprawnej implementacji tej metody.
Jak powinna wyglądać – to bardzo indywidualna kwestia, zależna od potrzeb. W najbardziej podstawowej wersji jest to porównanie ze sobą każdego z pól tej klasy osobno.

Dla poprzedniego przykładu ze Stringami:

HashCode


Jest to metoda zwracająca typ prosty liczbowy – int.

Jest sposobem na (niejednoznaczną!) identyfikację obiektu, tzn. jednakowe obiekty muszą mieć ten sam hashcode.
Ale ten sam hashcode nie musi oznaczać, że obiekty są jednakowe – zależnie od sposobu generowania wartości hasha, wartości te mogą się duplikować, zwłaszcza że zakres int jest ograniczony do pewnego przedziału.
Z tego względu metoda equals() nie może opierać się tylko i wyłącznie na metodzie hashCode(), obie metody muszą być zaimplementowane odrębnie i w sposób specyficzny dla danego typu obiektu.

Najlepsza implementacja metody hashCode() bazuje w jakiś sposób na wszystkich kluczowych polach klasy, tak aby zmiana któregokolwiek z nich spowodowała wygenerowanie innego hasha i to w taki sposób, że rozkład tych wartości w zakresie dziedziny int jest jak najbardziej równomierny.

Poprawna implementacja hashCode() jest kluczowa przy używaniu niektórych struktur danych, takich jak HashMap czy HashSet (więcej o strukturach danych w kolejnym artykule).

Kontrakt


Przede wszystkim: jeśli implementujemy equals(), to powinniśmy też hashCode() i odwrotnie.

equals: (wpis w dokumentacji)
  • zwrotna – x.equals(x) zwraca true
  • symetryczna – jeśli x.equals(y) to y.equals(x)
  • przechodnia – jeśli x.equals(y) i y.equals(z), to x.equals(z)
  • spójna (deterministyczna) – jeśli żadna z kluczowych informacji się nie zmieni, zwracana wartość musi pozostać ta sama
  • equals(null) zwraca false
hashCode: (wpis w dokumentacji)
  • deterministyczna (choć niekoniecznie między odrębnymi uruchomieniami programu) – jeśli żadna z kluczowych informacji używanych w equals się nie zmieni, zwracana wartość musi pozostać ta sama
  • jeśli equals zwraca true, to oba obiekty muszą zwracać ten sam hashcode
  • hashCode może być zwracany ten sam dla różnych obiektów (choć w większości przypadków lepiej żeby nie był)

Domyślna implementacja


Jeśli nie napiszemy własnej implementacji w klasie ani nie dziedziczymy po klasie, która ma te metody zaimplementowane, to nasza klasa domyślnie odziedziczy je po klasie Object.
Domyślna implementacja zależy od konkretnej wersji JVM.

equals:
Używa po prostu operatora ==, porównując adres w pamięci.

hashCode:
Zwykle jest to po prostu metoda przekształcająca adres pamięci na int.

Informatyk, programista. Obecnie Java Developer (Web Fullstack), właściciel studia Berrygames oraz prezes koła TK Games na Politechnice Wrocławskiej.

Wszystkie artykuły autora>>

Dodaj komentarz