W Javie metody equals() i hashCode() są niezwykle ważne dla prawidłowego działania kolekcji, takich jak HashMap, HashSet i innych. To pytanie często umyka początkującym programistom, chociaż naruszenie kontraktów tych metod prowadzi do trudnych do zidentyfikowania błędów w logice aplikacji.
Historia pytania:
W języku Java wszystkie klasy dziedziczą metody equals() i hashCode() z klasy Object. Domyślnie, equals() porównuje referencje na obiekty (czyli ich fizyczne położenie w pamięci), a hashCode() zwraca unikalny kod dla każdego obiektu. Jednak dla klas użytkowników często konieczne jest porównywanie obiektów na podstawie ich zawartości, a nie referencji.
Problem:
Jeśli metody equals() i hashCode() nie zostały przesłonięte lub zostały niepoprawnie przesłonięte, obiekty mogą działać w sposób niespodziewany w kolekcjach opartych na haszowaniu. Może to prowadzić do braku elementów, duplikatów lub błędów podczas wyszukiwania.
Rozwiązanie:
Zawsze przesłaniaj obie metody razem, ściśle przestrzegając kontraktu:
a.equals(b) == true, to a.hashCode() == b.hashCode()a.equals(b) == false, wymaganie dla hashCode jest takie, że unikalność nie jest obowiązkowaPrzykład poprawnej implementacji:
public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
Kluczowe cechy:
equals() powinna być reflektywna, symetryczna, przechodnia i spójna.hashCode() powinna zwracać tę samą wartość dla obiektu przy niezmiennych danych.Czy można używać tylko equals() bez hashCode() w klasach, które będą przechowywane w HashSet?
Nie. Jeśli przesłoniłeś tylko equals(), kolekcje oparte na haszowaniu nie będą prawidłowo określać unikalności obiektów. HashSet najpierw porównuje hashCode, a następnie equals.
Czy konieczne jest używanie wszystkich pól klasy w equals() i hashCode()?
Nie. Tylko te, które są istotne dla logicznej tożsamości klasy. Na przykład, jeśli obiekt ma wewnętrzny, unikalny identyfikator, wystarczy jego.
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); }
Czy można opierać się na getterze zamiast bezpośredniego pola w equals()?
Zwykle tak, jeśli nie ma efektów ubocznych i getter jest stabilny. Ale istnieje ryzyko, że getter zwraca różne wartości przy różnych wywołaniach — wtedy zachowanie będzie nieprzewidywalne.
hashCode() przy przesłoniętym equals().hashCode().Programista implementuje klasę User i definiuje tylko metodę equals(), zapominając o hashCode(). W wyniku dodawania i wyszukiwania obiektów w HashSet występują duplikaty i "gubią się" elementy.
Zalety:
Wady:
Programista implementuje obie metody ściśle według kontraktu, używa tylko id wewnątrz logiki równości i haszowania. Kolekcje zachowują się zgodnie z oczekiwaniami, wyszukiwanie i przechowywanie działają prawidłowo.
Zalety:
Wady: