Porównanie obiektów Java z equals () i hashcode ()

W tym programie Java Challenger nauczysz się, jak equals()i hashcode()łączyć, aby porównania obiektów były wydajne i łatwe w programach Java. Mówiąc najprościej, te metody działają razem, aby sprawdzić, czy dwa obiekty mają takie same wartości.  

Bez equals()i hashcode()musielibyśmy tworzyć " if" bardzo duże porównania, porównując każde pole z obiektu. To spowodowałoby, że kod byłby naprawdę zagmatwany i trudny do odczytania. Te dwie metody razem pomagają nam tworzyć bardziej elastyczny i spójny kod.

Pobierz kod źródłowy Java Challengers.

Zastępowanie równości () i hashcode () w Javie

Przesłanianie metod to technika, w której zachowanie klasy lub interfejsu nadrzędnego jest ponownie zapisywane (zastępowane) w podklasie w celu wykorzystania polimorfizmu. Każdy Objectjęzyk Java zawiera metodę equals()i hashcode()metodę, ale muszą one zostać zastąpione, aby działały poprawnie.

Aby zrozumieć, w jaki sposób nadpisywanie działa z equals()i   hashcode(), możemy zbadać ich implementację w podstawowych klasach Java. Poniżej znajduje się equals()metoda w Objectklasie. Metoda sprawdza, czy bieżące wystąpienie jest takie samo jak poprzednio przekazane Object.

 public boolean equals(Object obj) { return (this == obj); } 

Gdy hashcode()metoda nie zostanie zastąpiona, Objectzostanie wywołana metoda domyślna w klasie. Jest to metoda natywna , co oznacza, że ​​zostanie wykonana w innym języku, takim jak C, i zwróci kod dotyczący adresu pamięci obiektu. (Nie jest tak ważne, aby dokładnie wiedzieć, jak działa ta metoda, chyba że piszesz kod JDK).

 @HotSpotIntrinsicCandidate public native int hashCode(); 

Gdy metody equals()i hashcode()nie zostaną zastąpione, zamiast nich zostaną wywołane powyższe metody. W tym przypadku metody nie spełniają rzeczywistego celu programu equals()i hashcode(), którym jest sprawdzenie, czy dwa lub więcej obiektów ma takie same wartości.

Z reguły, gdy nadpisujesz equals(), musisz również nadpisywać hashcode().

Porównywanie obiektów z równymi ()

Używamy tej equals()metody do porównywania obiektów w Javie. Aby ustalić, czy dwa obiekty są takie same, equals()porównuje wartości atrybutów obiektów:

 public class EqualsAndHashCodeExample { public static void main(String... equalsExplanation) { System.out.println(new Simpson("Homer", 35, 120) .equals(new Simpson("Homer",35,120))); System.out.println(new Simpson("Bart", 10, 120) .equals(new Simpson("El Barto", 10, 45))); System.out.println(new Simpson("Lisa", 54, 60) .equals(new Object())); } static class Simpson { private String name; private int age; private int weight; public Simpson(String name, int age, int weight) { this.name = name; this.age = age; this.weight = weight; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Simpson simpson = (Simpson) o; return age == simpson.age && weight == simpson.weight && name.equals(simpson.name); } } } 

W pierwszym porównaniu equals()porównuje bieżącą instancję obiektu z przekazanym obiektem. Jeśli dwa obiekty mają takie same wartości, equals()zwróci true.

W drugim porównaniu equals()sprawdza, czy przekazany obiekt ma wartość null lub czy jest wpisany jako inna klasa. Jeśli jest to inna klasa, obiekty nie są równe.

Wreszcie equals()porównuje pola obiektów. Jeśli dwa obiekty mają takie same wartości pól, to obiekty są takie same.

Analiza porównań obiektów

Spójrzmy teraz na wyniki tych porównań w naszej main()metodzie. Najpierw porównujemy dwa Simpsonobiekty:

 System.out.println(new Simpson("Homer", 35, 120).equals(new Simpson("Homer", 35, 120))); 

Obiekty tutaj są identyczne, więc wynik będzie true.

Następnie Simpsonponownie porównujemy dwa obiekty:

 System.out.println(new Simpson("Bart", 10, 45).equals(new Simpson("El Barto", 10, 45))); 

Obiekty tutaj są prawie identyczne, ale ich nazwy są różne: Bart i El Barto. Dlatego wynik będzie false.

Na koniec porównajmy Simpsonobiekt i instancję klasy Object:

 System.out.println(new Simpson("Lisa", 54, 60).equals(new Object())); 

W tym przypadku wynik będzie taki, falseże typy klas są różne.

equals () versus ==

Na pierwszy rzut oka może się wydawać , że ==operator i equals()metoda robią to samo, ale w rzeczywistości działają inaczej. ==Operatora porównuje czy dwa obiekt punktu odniesienia do tego samego obiektu. Na przykład:

 System.out.println(homer == homer2); 

W pierwszym porównaniu utworzyliśmy dwie różne Simpsoninstancje przy użyciu newoperatora. Z tego powodu zmienne homeri homer2będą wskazywać na różne Objectodwołania w stercie pamięci. Więc otrzymamy falsewynik.

System.out.println(homer.equals(homer2)); 

W drugim porównaniu zastępujemy equals()metodę. W takim przypadku porównane zostaną tylko nazwy. Ponieważ nazwa obu Simpsonobiektów to „Homer”, wynik będzie true.

Unikalne identyfikowanie obiektów za pomocą hashcode ()

Używamy tej hashcode()metody do optymalizacji wydajności podczas porównywania obiektów. Wykonywanie   hashcode()zwraca unikalny identyfikator dla każdego obiektu w programie, co znacznie ułatwia zadanie porównywania całego stanu obiektu.

Jeśli hashcode obiektu nie jest taki sam jak hashcode innego obiektu, nie ma powodu, aby wykonywać tę equals()metodę: po prostu wiesz, że dwa obiekty nie są takie same. Z drugiej strony, jeśli kod skrótu jest taki sam, należy wykonać equals()metodę, aby określić, czy wartości i pola są takie same.

Oto praktyczny przykład z hashcode().

 public class HashcodeConcept { public static void main(String... hashcodeExample) { Simpson homer = new Simpson(1, "Homer"); Simpson bart = new Simpson(2, "Homer"); boolean isHashcodeEquals = homer.hashCode() == bart.hashCode(); if (isHashcodeEquals) { System.out.println("Should compare with equals method too."); } else { System.out.println("Should not compare with equals method because " + "the id is different, that means the objects are not equals for sure."); } } static class Simpson { int id; String name; public Simpson(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o)  if (this == o) return true; if (o == null  @Override public int hashCode() { return id; } } } 

A, hashcode()która zawsze zwraca tę samą wartość, jest poprawna, ale niezbyt skuteczna. W takim przypadku porównanie zawsze zwróci true, więc equals()metoda zawsze zostanie wykonana. W tym przypadku nie ma poprawy wydajności.  

Używanie equals () i hashcode () z kolekcjami

SetInterfejs jest odpowiedzialny za zapewnienie, zostaną wstawione w nie zduplikowane elementy Setpodklasy. Oto niektóre z klas implementujących Setinterfejs:

  • HashSet
  • TreeSet
  • LinkedHashSet
  • CopyOnWriteArraySet

Do elementu a można wstawiać tylko unikatowe elementy Set, więc jeśli chcesz dodać element do HashSetklasy (na przykład), musisz najpierw użyć metod equals()i hashcode(), aby sprawdzić, czy element jest unikalny. Gdyby metody equals()i hashcode()nie zostały w tym przypadku zastąpione, ryzykowałbyś wstawienie zduplikowanych elementów w kodzie.

W poniższym kodzie używamy addmetody, aby dodać nowy element do HashSetobiektu. Przed dodaniem nowego elementu HashSetsprawdza, czy element już istnieje w danej kolekcji:

 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; 

Jeśli obiekt jest taki sam, nowy element nie zostanie wstawiony.

Kolekcje haszujące

Setnie jest jedyną kolekcją, która wykorzystuje equals()i hashcode(). HashMap, Hashtable i LinkedHashMap również wymagają tych metod. Z reguły, jeśli zobaczysz kolekcję, która ma przedrostek „Hash”, możesz być pewien, że wymaga ona zastąpienia metod hashcode()i equals(), aby ich funkcje działały poprawnie.  

Wskazówki dotyczące używania equals () i hashcode ()

equals()Metodę należy wykonywać tylko dla obiektów, które mają ten sam unikalny identyfikator kodu skrótu. Należy nie wykonać equals(), gdy ID hashcode jest inna.

Tabela 1. Porównania Hashcode

Jeśli hashcode()porównanie ... Następnie …
zwraca prawdę wykonać equals()
zwraca false nie wykonuj equals()

This principle is mainly used in Set or Hash collections for performance reasons.

Rules for object comparison

When a hashcode() comparison returns false, the equals() method must also return false. If the hashcode is different, then the objects are definitely not equal.

Table 2. Object comparison with hashcode()

When the hashcode comparison returns ... The equals() method should return ...
true true or false
false false

When the equals() method returns true, it means that the objects are equal in all values and attributes. In this case,  the hashcode comparison must be true as well.

Table 3. Object comparison with equals()

When the equals() method returns ... The hashcode() method should return ...
true true
false true or false

Take the equals() and hashcode() challenge!

It’s time to test your skills with the equals() and hashcode() methods.  Your goal in this challenge is to figure out the output of the two equals() method comparisons and guess the size of the Set collection.

To start, study the following code carefully:

 public class EqualsHashCodeChallenge { public static void main(String... doYourBest) { System.out.println(new Simpson("Bart").equals(new Simpson("Bart"))); Simpson overriddenHomer = new Simpson("Homer") { public int hashCode() { return (43 + 777) + 1; } }; System.out.println(new Simpson("Homer").equals(overriddenHomer)); Set set = new HashSet(Set.of(new Simpson("Homer"), new Simpson("Marge"))); set.add(new Simpson("Homer")); set.add(overriddenHomer); System.out.println(set.size()); } static class Simpson { String name; Simpson(String name) { this.name = name; } @Override public boolean equals(Object obj) { Simpson otherSimpson = (Simpson) obj; return this.name.equals(otherSimpson.name) && this.hashCode() == otherSimpson.hashCode(); } @Override public int hashCode() { return (43 + 777); } } } 

Remember, analyze the code first, guess the result, and then run the code. Your goal is to improve your skill with code analysis and absorb core Java concepts to make your code more powerful. Choose your answer before checking the correct answer below.

 A) true true 4 B) true false 3 C) true false 2 D) false true 3 

What just happened? Understanding equals() and hashcode()

In the first equals() method comparison, the result is true because the state of the object is exactly the same and the hashcode() method returns the same value for both objects.

In the second equals() method comparison, the hashcode() method is being overridden for the overridenHomer variable. The name is “Homer” for both Simpson objects, but the hashcode() method returns a different value for overriddenHomer. In this case, the final result from the the equals() method will be false because the method contains a comparison with the hashcode.

You might notice that the size of the collection is set to hold three Simpson objects. Let’s check this in a detailed way.

The first object in the set will be will be inserted normally:

 new Simpson("Homer"); 

The next object will be inserted normally, as well, because it holds a different value from the previous object:

 new Simpson("Marge"); 

Finally,  the following Simpson object has the same value as the first object. In this case the object won’t be inserted:

 set.add(new Simpson("Homer")); 

Jak wiemy, overridenHomerobiekt używa innej wartości hashcode niż normalna Simpson(“Homer”)instancja. Z tego powodu ten element zostanie wstawiony do kolekcji:

 overriddenHomer; 

Klucz odpowiedzi

Odpowiedź na to jest Java challenger B . Wynik byłby następujący:

 true false 3 

Wyzwanie wideo! Debugowanie równa się () i hashcode ()

Debugowanie to jeden z najłatwiejszych sposobów pełnego przyswojenia pojęć programistycznych przy jednoczesnym ulepszaniu kodu. W tym filmie możesz śledzić, jak debuguję i wyjaśniam Javę equals()i hashcode()wyzwanie.