Java HR: metody equals() i hashCode()
Jak wiadomo, każdy obiekt w Javie dziedziczy po klasie Object.
- 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 equals i hashCode, 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 == i != służą przede wszystkim do porównywania ze sobą typów prostych (prymitywów)
1 2 3 4 5 6 |
int a = 5; int b = 5; int c = 3; a == b //returns: true a == c //returns: false |
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.
1 2 3 4 5 |
Object obj1 = new Object(); Object obj2 = new Object(); obj1 == obj1 //returns: true obj1 == obj2 //returns: false |
1 2 3 4 |
String str1 = "hello" String str2 = "hello" str1 == str2 //returns: true (!) |
Ale jednocześnie:
1 2 3 4 |
String str1 = new String("hello"); String str2 = new String("hello"); str1 == str2 //returns: false (!) |
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.
1 2 3 4 5 |
SomeObject so1 = new SomeObject(3, 5); SomeObject so2 = new SomeObject(3, 5); so1 == so2 //returns false so1.equals(so2) //returns true |
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.
1 2 3 4 5 |
String str1 = "hello" String str2 = "hello" str1 == str2 //returns: true str1.equals(str2) //returns: true, klasa String posiada już odpowiednią implementację metody |
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.
- 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
- 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.