The equals()
and hashCode()
methods help compare objects. Without them, we would have to use many if
to compare each object's fields separately. And thanks to equals()
and hashCode()
, you can make the code easier to read and understand with no unnecessary constructs.
The equals()
method is needed to compare objects with each other. In the standard implementation, the method compares one object with the current object. If the references to them are equal, it returns True; if they are not, it returns False.
In turn, hashCode()
generates the integer code of the class instance. If you are overriding equals()
in Java, it is necessary to override hashCode()
otherwise you may encounter errors.
In the standard implementation, equals()
looks like this:
public boolean equals(Object obj) {
return (this == obj);
}
Let's see how this works in practice. The following example shows a User
class with two variables: nickname
and rating
. We create two instances, pass the same values to them, and compare them:
class User {
private String nickname;
private int rating;
User(String nickname, int rating){
this.nickname = nickname;
this.rating = rating;
}
}
public class Difference {
public static void main(String[] args) {
User user1 = new User("Andrew", 250);
User user2 = new User("Andrew", 250);
//Compare two objects and display the result
boolean bool = user1.equals(user2);
System.out.println(bool);
}
}
By default, the equals()
method returns True only if the references of two objects are equal. Therefore, the program from the example above will return False; they have different links.
The problem is that we are not solving the real problem with this implementation. Let's say the application's business logic requires the state of objects be checked. It is visually clear that the fields match. This is the same user with the same rating. However, standard behavior leads to the opposite result.
Overriding the equals()
method in Java helps correct this shortcoming. The essence of this mechanism is to change the behavior of the equals()
method of the parent class in the child class. It's easier to understand with an example.
Let's see how to override the equals()
method in Java and write our own state comparison logic:
class Complex {
private double number1, number2;
public Complex(double number1, double number2) {
this.number1 = number1;
this.number2 = number2;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj.getClass() != this.getClass()) {
return false;
}
Complex c = (Complex) obj;
return Double.compare(number1, c.number1) == 0
&& Double.compare(number2, c.number2) == 0;
}
}
public class Main {
public static void main(String[] args) {
Complex example1 = new Complex(20, 15);
Complex example2 = new Complex(20, 15);
if (example1.equals(example2)) {
System.out.println("Equal ");
} else {
System.out.println("Not Equal ");
}
}
}
Output:
Equal
The @Override
annotation tells the compiler to override the method during compilation. It is worth considering that without an annotation, overriding a method will also work if the compiler finds a method with the same signature in the parent class. However, having an annotation is useful for controlling this action and making the code readable. If we add an annotation to a method that is not in the parent class, we will get an error when building the application.
The method itself now consists of three parts. Let's take a closer look at them.
If the object is compared to itself, it should return True:
if (obj == this) {
return true;
}
Let's see if the object belongs to the Complex
class. Return False if this is not the case:
if (obj.getClass() != this.getClass()) {
return false;
}
We cast the instance type to Complex
, compare the elements and return the match:
Complex c = (Complex) obj;
return Double.compare(number1, c.number1) == 0
&& Double.compare(number2, c.number2) == 0;
}
When changing how a method works, you must follow the rules for overriding equals()
in Java:
If an object is compared to itself, True should be returned.
If the object is compared to null, False must be returned.
If two objects are equal, Obj1.equals(Obj2)
and Obj2.equals(Obj1)
must return True.
When comparing three objects, if Obj1.equals(Obj2)
and Obj2.equals(Obj3)
return True, then Obj1.equals(Obj3)
should return True.
Calling a method multiple times should return the same result as long as the object's properties in your implementation do not change.
There are also some restrictions in Java on overriding equals()
. For example, there is no point in overriding a method if each object is unique. Additionally, this applies to classes that are not designed to work with data, but to provide specific behavior.
Another situation when a method is not overridden is the use of a class whose instances are pointless to compare. A good example is java.util.Random
. The essence of this class is to return random sequences of numbers. Instances of this class must not be equal; otherwise, there is no point to them.
When you change the logic of equals()
, it is highly recommended also to override the logic of hashCode()
. If you don't do this, identical objects may have different hash codes. For this reason, hash-based collections, for example, will not perform as expected.
Because hashCode()
generates a unique identifier, comparing the states of objects becomes easier. If the identifiers are different, equals()
may not be run at all. If the identifiers are the same, you need to run equals()
and check the properties of the objects.
A bad example of overriding hashCode()
is returning a constant. For example, like this:
@Override
public int hashCode() {
return 35;
}
In practice, this creates huge problems. The hash value will not change when the state changes. Let's say you change the field values. The hash code will remain the same.
Only those fields that are used in equals()
should participate in determining a hash value. In addition, you need a prime - the basis for calculating the hash. Typically, 31 is used as a prime, but you can set it to any other value.
Calculation rules:
result
variable is assigned a non-zero value, for example, the number 31.boolean
- (f ? 1 : 0)
;byte
, char
, short
or int
- (int) f
;long
- (int)(f ^ (f >>> 32))
;float
- Float.floatToIntBits(f)
;double
- Double.doubleToLongBits(f)
, and then the same as for long
;hashCode()
;null
- return 0
;After processing each field, you must add the result to the prime and previous results. After going through all the fields, return the final hash code.
Let's say you want to override hashCode()
for the Person
class:
public class Person {
private int age;
private int number;
private double salary;
private String name;
private CarKey carKey;
public Person(int age, int number, String name, double salary, CarKey carKey) {
this.age = age;
this.number = number;
this.name = name;
this.salary = salary;
this.carKey = carKey;
}
@Override
public int hashCode() {
int result = 31;
result = result * 17 + age;
result = result * 17 + number;
long lnum = Double.doubleToLongBits(salary);
result = result * 17 + (int)(lnum ^ (lnum >>> 32));
result = result * 17 + name.hashCode();
result = result * 17 + carKey.hashCode();
return result;
}
// Here you can already override equals()
//...
}
Starting with Java 7, additional methods are available for creating your own hashCode()
implementation. For example, for the same Person
class, it is enough to execute:
@Override
public int hashCode() {
return Objects.hash(age, number, salary, name, carKey);
}
For hashCode()
, there are also strict override rules:
Multiple calls to hashCode()
return the same integer value until one of the properties used in your version of equals()
changes. However, the hash code may change after stopping and starting the application.
If instances of a class are equal according to the equals()
method, their hash codes must also be the same.
If the instances are not equal according to equals()
, the hashCode()
method will not necessarily return different values. However, returning different values for different objects is a good practice and has a positive impact on the performance of hash tables.
Follow these rules when writing your versions of equals()
and hashCode()
. Remember that methods must be overridden together, otherwise you may end up with instances with the same state being defined as different.
The equals()
method can be overridden to compare field values by matching the states of the instances.
If comparing two hash codes returns False, then the result of equals()
must return False.
If you are creating your own equals()
implementation, change the hashCode()
implementation.
When using collections that use hash tables, override both methods, otherwise there may be duplicate elements in the collection.