blog.christoffer.online
Hi, my name is Christoffer and I am an Internet addict.

One does not simply override Object.equals in Java

2010-06-25 09:48

Today as I ran the the static code analysis tool Checkstyle on the company's source code to check and try and resolve as many bad smells as I could. One of the checks we use is the CovariantEquals check which basically checks the source code and make sure that if you have defined a covariant equals() method in a class, you must also override the Object.equals(java.lang.Object) method.

It is really easy to forget this since you once you have implemented your own equals-method you think you have actually implemented the Object.equals-method, which you actually haven't. Believing this is dangerous and could result in unexpected runtime behaviour. Let me show you why with an easy demonstration.

Below is a simple Java class called Person that has a first- and surname.

public class Person {

  private String firstname;
  private String surname;


  public Person( String firstname, String surname ) {

    this.firstname = firstname;
    this.surname = surname;
  }


  public String getFirstname() {
    return firstname;
  }


  public String getSurname() {
    return surname;
  }


  public boolean equals( Person anotherPerson ) {
    if( anotherPerson == null ) {
      return false;
    }

    return this.getFirstname().equals(anotherPerson.getFirstsame()) && this.getSurname().equals(anotherPerson.getSurname());

  }

}

As you can see that I have created my own equals-method that simply compares their names.

Since I have implemented my own equals-method I also define that this is how to check if two Persons are the same. If they have the same first- and surname, I assume it's the same person.

This means that this example print out true:

Person a = new Person("John", "Doe");
Person b = new Person("John", "Doe");

System.out.println( a.equals(b) );

Even if we use a List and check if the list contains the person:

List people = new ArrayList();
people.add(a);

System.out.println( people.contains(b) );

However, if we forget to put generics or if you still develop in Java 1.4.x (if you still do, you seriously need to upgrade) it would fail:

List people = new ArrayList();
people.add(a);

System.out.println( people.contains(b) );

It will also fail i f you make a wild card generics:

List people = new ArrayList();
people.add(a);

System.out.println( people.contains(b) );

Why? Because the contains-method will no longer keep track of it's two Persons that

are getting compared, but two Objects. Meaning that Object.equals(Object) will be called instead. If you fetch the Java source code for Object you will see that Object.equals() actually checks if the two Objects are the same:

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

So you might go "Fine, I'll just override the Object.equals(Object) method. No biggie.".

public boolean equals(Object object) {
  return (object instanceof Person) ? equals((Person) object) : false;
}

Now you check that if the incoming object is actually a Person and if that is the case you will call your covariant equals-method and return true. But this means that you will never be able to check if two Persons are actually the same Person-objects by using the equals-method:

However, you can still use the "equal to" operator to control that, but the most common coding convention to compare two Objects is by using the equals-method. You have to decide what you want.

In almost all situations, especially for big software teams, creating your own equals-method will do more harm than good and you should only do it when you know and can guarantee that class will never be used in an incorrect or unexpected situation

So what is my conclusion? Don't implement the equals-method. Create a new method and call it something else, such as 'equalsTo', and leave the Object-equals method to Java for comparing Objects.