Log In

Overriding the equals() Method in Java

Overriding the equals() Method in Java
06.03.2024
Reading time: 8 min
Hostman Team
Technical writer

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.

The standard implementation of equals()

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.

Overriding equals()

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;
    }

Override Rules

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.

Overriding hashCode()

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:

  • The result variable is assigned a non-zero value, for example, the number 31.
  • A hash is calculated for each significant field of the instance. The calculation rules differ depending on the field type:
    • for boolean - (f ? 1 : 0);
    • for byte, char, short or int - (int) f;
    • for long - (int)(f ^ (f >>> 32));
    • for float - Float.floatToIntBits(f);
    • for double - Double.doubleToLongBits(f), and then the same as for long;
    • for fields that are a reference to another object - a recursive call to hashCode();
    • for null - return 0;
    • for an array - treat as if each element were a separate field of the object.

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.

What to remember

  • 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.


Share