How to Implement the equals() Method

Quoting Javadoc for equals() method:

The equals method implements an equivalence relation on non-null object references:
  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

The first three conditions (reflexive,symmetric, and transitive), say that the equals() method is an equivalence relation on all non-null Objects. (Note: not on Objects of a certain type, but on all Objects.) It means that the universe of all Objects is sliced into groups of equal (according to the equals() method) values. In other words, there is a set of buckets and every Object belongs to exactly one bucket. Objects within one bucket are equal.

If your equals() method does not satisfy the requirements, horrible things can happen because you no longer have this nice partitioning. This can lead to bugs that are very subtle and very tricky to debug.

Let's think about the simplest Java class possible, a Java bean with one field.

public class Person {
    protected String name;
    
    // standard getters and setters
    // ...
}

The most common pattern for the equals() method in a bean object is to compare values of all fields. If all fields are equal, then so are the objects. There are basically two ways people choose to implement equals() method.

 

Method 1: use getClass()

This is the way Eclipse uses by default to generate the equals() method. (You know that Eclipse can generate the equals() method for you, right?)

public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;

    Person other = (Person) obj;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;

    return true;
}

Method 2: use instanceof

This is the method Joshua Bloch introduces in his book "Effective Java".

public boolean equals(Object obj) {
    if (this == obj) 
        return true;
    if (!(o instanceof Person)) {
        return false;
    }

    Person other = (Person) obj;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name)) {
        return false;
    }   

    return true;
}

What are the differences between the two pieces of code?

Minor difference

The first difference is that the instanceof way does not check for null values. This is just a Java trick though: it's because null instanceof Person (and null instanceof AnyOtherClass) is false.

Major difference

The real difference is the way both methods check for type equality. The first method uses getClass(), the other method uses instanceof. The two methods differ in the way subclasses are treated. Imagine we have a subclass Student.

Consider two objects: Person(Agnieszka) and Student(Agnieszka). In the first approach, the two objects are not equal. The getClass() method returns "class Person" and "class Student", respectively. In the second approach, the two objects are equal, because Student is an instance of Person.

Which one is right?

The choice between the two methods is a topic of fiery discussions on many Java forums.

The argument against the instanceof approach is simple: it breaks the transitivity. Imagine we have a second subclass, Programmer.

Now Person(Agnieszka) and Student(Agnieszka) are equal.

Person(Agnieszka) and Programmer(Agnieszka) are equal too.

But Student(Agnieszka) and Programmer(Agnieszka) are NOT equal.

The instanceof approach violates the transitivity requirement of the equals() method. It doesn't happen in the getClass() approach. (However, if you do not allow the addition of subclasses, e.g., Person class is final, then the instanceof approach is OK, too.)

Why do people use the instanceof approach? One of the reasons is the authority of Joshua Bloch. He popularized this approach in chapter 3 of "Effective Java". The arguments he gives are the Liskov substitution principle and the principle of least astonishment. Well, Alex Blewitt argues that the Liskov substitution principle does not hold in Java.  Personally, I would be astonished if I found out Person(Agnieszka) and Student(Agnieszka) to be equal.

Another reason why the instanceof approach is popular is Hibernate. Hibernate uses proxy objects which are subclasses of real objects. If you want the proxy object to be equal to the real object you have to use instanceof. The Hibernate lobby has even convinced the Eclipse community to add instanceof approach to its equals() generator. It's sad that Hibernate design flaws are introducing non-features into other, innocent products. It's especially harmful if it's a popular IDE, such as Eclipse. Many programmers will believe that since the instanceof implementation of equals() is an option in Eclipse, it is correct. Only a handful will bother to investigate the difference between the two approaches. That's how this bad practise is spreading in the Java community.

OneWebSQL uses the getClass() approach in its implementation of equals(). However, in the canonical use of OneWebSQL classes, there is no need to subclass bean objects. So in a sense the getClass()/instanceof discussion is irrelevant for us. Still, OneWebSQL classes satisfy the requirements for the equals() method. Moreover, the implementation of equals() is compatible with the implementation of the hashCode() method.

Read more

A very thorough discussion on various ways to implement equals() can be found on the Angelika Langer website. Part 2 deals with how to allow mixed-type comparisons.

How do you implement your equals() method?