📜 ⬆️ ⬇️

Java Challengers # 4: Comparing objects with equals () and hashCode ()

Java Challengers # 4: Comparing objects with equals () and hashCode ()


In the run-up to the launch of the new Java developer flow , we continue to translate a series of Java Challengers articles, the previous parts of which can be found in the links below:



Go!


In this article, you will learn how the equals() and hashCode() methods are related to each other and how they are used when comparing objects.


equals-hashcode


Without using equals() and hashCode() to compare the state of two objects, we need to write a lot of " if " comparisons, comparing each field of the object. This approach makes the code confusing and difficult to read. Working together, these two methods help to create more flexible and consistent code.


The source code for the article is here .


Override equals () and hashCode ()


Method overriding (method overriding) is a technique in which the behavior of the parent class or interface is rewritten (redefined) in a subclass (see Java Challengers # 3: Polymorphism and Inheritance , eng ). In Java, each object has the equals() and hashCode() methods and they must be redefined for proper operation.


To understand how the equals() and hashCode() override works, let's examine their implementation in Java base classes. Below is the equals() method of the Object class. The method checks whether the current instance matches the passed obj object.


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

Now let's take a look at the hashCode() method in the Object class.


 @HotSpotIntrinsicCandidate public native int hashCode(); 

This is a native method that is written in another language, such as C, and it returns some numeric code associated with the memory address of the object. (If you are not writing JDK code, then it is not important to know exactly how this method works.)
Translator's note: the value associated with the address is not entirely correct ( thanks to vladimir_dolzhenko ). HotSpot JVM uses pseudo-random numbers by default. The implementation description of hashCode () for HotSpot is here and here .


If the equals() and hashCode() methods are not overridden, the Object class methods described above will be called instead. In this case, the methods do not fulfill the real goal of equals() and hashCode() , which is to check whether objects have the same state.


As a rule, when equals() is overridden, hashCode() also overridden.


Comparing objects with equals ()


The equals() method is used to compare objects. To determine whether objects are the same or not, equals() compares the field values ​​of the objects:


 public class EqualsAndHashCodeExample { public static void main(String... args){ 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) { // 1 if (this == o) { return true; } // 2 if (o == null || getClass() != o.getClass()) { return false; } // 3 Simpson simpson = (Simpson) o; return age == simpson.age && weight == simpson.weight && name.equals(simpson.name); } } } 

Let's look at the equals() method. The first comparison compares the current instance of this object with the passed o object. If this is the same object, then equals() returns true .


In the second comparison, it is checked whether the object passed in is null and what type it is. If the transferred object is of a different type, then the objects are not equal.


Finally, equals() compares the fields of objects. If two objects have the same field values, then the objects are the same.


Analysis of options for comparing objects


Now let's look at the options for comparing objects in the main() method. First, we compare two Simpson objects:


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

The fields of these objects have the same values, so the result will be true .


Then again compare two Simpson objects:


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

The objects here are similar, but the meanings of the names are different: Bart and El Barto . Therefore, the result will be false .


Finally, let's compare the Simpson object and an instance of the Object class:


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

In this case, the result will be false , since the object types differ.


equals () versus ==


At first glance, it seems that the == operator and the equals() method do the same thing, but, in fact, they work differently. The == operator compares whether two references point to the same object. For example:


 Simpson homer = new Simpson("Homer", 35, 120); Simpson homer2 = new Simpson("Homer", 35, 120); System.out.println(homer == homer2); 

We created two different instances of Simpson using the new operator. Therefore, the variables homer and homer2 will point to different objects in the heap . Thus, as a result, we get false .


In the following example, use the override method equals() :


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

In this case, the fields will be compared. Since the field values ​​of both Simpson objects are the same, the result will be true .


Identifying objects with hashCode ()


To optimize performance when comparing objects, use the hashCode() method. The hashCode() method returns a unique identifier for each object, which makes it easier to compare object states.


If the hash code of the object does not match the hash code of another object, then you can not run the equals() method: you just know that the two objects do not match. On the other hand, if the hash code is the same, then you need to run the equals() method to determine if the field values ​​match.


Consider a practical example with hashCode() .


 public class HashcodeConcept { public static void main(String... args) { Simpson homer = new Simpson(1, "Homer"); Simpson bart = new Simpson(2, "Homer"); boolean isHashcodeEquals = homer.hashCode() == bart.hashCode(); if (isHashcodeEquals) { System.out.println("   equals."); } else { System.out.println("    equals, .. " + " ,  ,     ."); } } 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 || getClass() != o.getClass()) return false; Simpson simpson = (Simpson) o; return id == simpson.id && name.equals(simpson.name); } @Override public int hashCode() { return id; } } } 

The hashCode() method, which always returns the same value, is valid, but not effective. In this case, the comparison will always return true , so the equals() method will always be executed. In this case, there is no performance improvement.


Using equals () and hashCode () with collections


Classes that implement the Set (set) interface should prevent the addition of duplicate elements. Below are some classes that implement the Set interface:



Only unique elements can be added to the Set . Thus, if you want to add an element, for example, in a HashSet , you must first use the equals() and hashCode() methods to make sure that this element is unique. If the equals() and hashCode() methods were not overridden, you risk inserting duplicate values.


Let's look at part of the implementation of the add() method in HashSet :


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

Before adding a new item, HashSet checks if an item exists in this collection. If the object matches, the new element will not be inserted.


The equals() and hashCode() methods are used not only in Set . These methods are also required for HashMap , Hashtable , and LinkedHashMap . As a rule, if you see a collection with the prefix "Hash" , you can be sure that its correct operation requires redefinition of the hashCode() and equals() methods.


Recommendations for using equals () and hashCode ()


Run the equals() method only on objects with the same hash code. Do not use equals() if the hash code is different.


Table 1. Comparison of hash codes


If comparing hashCode () ...That ...
returns trueexecute equals()
returns falsedo not perform equals()

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


Rules for comparing objects


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


Table 2. Comparison of objects with hashCode ()


When comparing hashCode() returns ...The equals() method should return ...
truetrue or false
falsefalse

When the equals() method returns true , it means that objects are equal in all values ​​and attributes . In this case, the hash code comparison should also be true.


Table 3. Comparing objects with equals ()


When the equals() method returns ...The hashCode() method should return ...
truetrue
falsetrue or false

Solve the problem on equals () and hashCode ()


It's time to test your knowledge of the equals() and hashCode() methods. The task is to find out the result of several equals() and the final size of the Set collection.


To get started, carefully review the following code:


 public class EqualsHashCodeChallenge { public static void main(String... args) { 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); } } } 

First, analyze the code, think about what the result will be. And only then run the code. The goal is to improve your code analysis skills and learn basic Java concepts so you can make your code better.


What will be the result ?.


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

What happened? Understanding equals () and hashCode ()


In the first comparison, the result of equals() is true , because the states of the objects are the same, and the hashCode() method returns the same value for both objects.


In the second comparison, the hashCode() method was redefined for the hashCode() variable. For both Simpson objects, the name is "Homer" , but for overriddenHomer the hashCode() method returns a different value. In this case, the result of the equals() method will be false , since it contains a comparison with the hash code.


You must have understood that there will be three Simpson objects in the collection. Let's break it down.


The first object in the set will be inserted as usual:


 new Simpson("Homer"); //  

The following object will also be inserted in the usual order, because it contains a value that is different from the previous object:


 new Simpson("Marge"); //  

Finally, the next Simpson object has the same name value as the first object. In this case, the object will not be inserted:


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

As we know, the overridenHomer object uses a different hash code value, as opposed to a regular Simpson("Homer") instance Simpson("Homer") . For this reason, this item will be inserted into the collection:


 set.add(overriddenHomer); //  

Answer


The correct answer is B. The output will be:


 true false 3 

Common errors with equals () and hashCode ()



Things to remember about equals () and hashCode ()



Learn more about Java.



Traditionally, I wait for your comments and invite you to an open lesson , which our teacher, Sergey Petrelevich , will conduct on March 18


')

Source: https://habr.com/ru/post/443710/


All Articles