Skip to content
December 13, 2011 / Keyhole Software

Implementing .equals

Scenario:
You are working on a project and you have two objects. You want to know if, according to the business, they are the same item. So you call .equals. It returns false because they aren’t the same object in memory. You then override the .equals method to compare the attributes that make the items equal to the business. This opens up a can of worms. 

Why? .Equals is used by the system for many things– overriding it should not be taken lightly. If you have no reason to make HashMaps, or consider the items the same key, then you probably don’t want to override .equals. Instead, do something that makes sense to the business– for example, making a new method like .isTheSameAs.

I have seen many good programmers implement a bad .equals in Java. So what makes a good implementation of .equals? Well first, it has to meet the following contract:

  • The hashCode method must return the same integer value every time it is invoked on the same object during the entire execution of a Java application or applet. It need not return the same value for different runs of an application or applet. The Java 2 platform (Java 2) documentation further allows the hashCode value to change if the information used in the equals method changes.
  • If two objects are equal according to the equals method, they must return the same value from hashCode.
  • The equals method is reflexive, which means that an object is equal to itself: x.equals( x ) should return true.
  • The equals method is symmetric: If x.equals( y ) returns true, then y.equals( x ) should return true also.
  • The equals method is transitive: If x.equals( y ) returns true and y.equals( z ) returns true, then x.equals( z  should return true.
  • The equals method is consistent. x.equals( y ) should consistently return either true or false. The Java 2 javadoc clarifies that the result of x.equals( y ) can change if the information used in the equals comparisons change.
  • Finally, x.equals(null) should return false.

Most experienced programmers know the rules of .equals and .hashCode. However, they can implement something that meets the Java contract but still causes problems. In an attempt to make things better, the Eclipse IDE has a code assist that will generate a method that implements a .equals and a .hashCode that meet the contract. But, this makes the danger worse because programmers feel confident the implementation is safe.

Remember, just because a .equals and .hashCode implementation meets the contract doesn’t mean it is safe. If the attributes used in the .equals and .hashCode are mutable then the hashCode can, and should (according to the contract), change. Why is this so bad?  If the object is a key in a HashMap or HashTable and that object changes after being used as a key, the value it references is lost in the HashMap or HashTable.

Consider this example which uses the Eclipse generated .equals and .hashCode.

public class Name {
            private String firstName;
            private String lastName;
            private String middleInitial;
            @Override
            public boolean equals(Object obj) {
                        if (this == obj)
                                    return true;
                        if (obj == null)
                                    return false;
                        if (getClass() != obj.getClass())
                                    return false;
                        final Name other = (Name) obj;
                        if (firstName == null) {
                                    if (other.firstName != null)
                                                return false;
                        } else if (!firstName.equals(other.firstName))
                                    return false;
                        if (lastName == null) {
                                    if (other.lastName != null)
                                                return false;
                        } else if (!lastName.equals(other.lastName))
                                    return false;
                        if (middleInitial == null) {
                                    if (other.middleInitial != null)
                                                return false;
                        } else if (!middleInitial.equals(other.middleInitial))
                                    return false;
                        return true;
            }
            public String getFirstName() {
                        return firstName;
            }
            public String getLastName() {
                        return lastName;
            }
            public String getMiddleInitial() {
                        return middleInitial;
            }
            @Override
            public int hashCode() {
                        final int prime = 31;
                        int result = 1;
                        result = prime * result
                                                + ((firstName == null) ? 0 : firstName.hashCode());
                        result = prime * result
                                                + ((lastName == null) ? 0 : lastName.hashCode());
                        result = prime * result
                                                + ((middleInitial == null) ? 0 : middleInitial.hashCode());
                        return result;
            }
            public void setFirstName(String firstName) {
                        this.firstName = firstName;
            }
            public void setLastName(String lastName) {
                        this.lastName = lastName;
            }
            public void setMiddleInitial(String middleInitial) {
                        this.middleInitial = middleInitial;
            }

}
package evil.equals;

import java.util.Hashtable;

public class NameTest extends Name {

            public static void main(String[] args) {
                        Name aName = new Name();
                        aName.setFirstName("Brad");
                        aName.setMiddleInitial("K");
                        aName.setLastName("Mongar");
                        System.out.println("hash:" + aName.hashCode());

                        Hashtable<Name, String> aHashtable =  new Hashtable<Name, String>();
                        aHashtable.put(aName, "aValue");

                        aName.setFirstName("Bradley");
                        System.out.println("hash:" + aName.hashCode());

                        if (aHashtable.containsKey(aName)){
                                    System.out.println("found");
                        } else {
                                    System.out.println("not found");
                        }
             }
}

If you run the code you get an output of:

hash:603748753
hash:-455745749
not found

You can see that the value “value” is now stranded in the HashTable. This is something to think about before you override .equals. 

As a side note, you may also be thinking “Well, I’m not going to use the class as a key in a Hash\*…” But keep in mind that objects are often used as keys in maps, persistence layers, or other architectural constructs you may not be aware of.

Simply put, my advice is that unless you make an object immutable, think twice or thrice before you implement a .equals.

–Brad Mongar, asktheteam@keyholesoftware.com

6 Comments

Leave a Comment
  1. Phil Ledgerwood / Dec 13 2011 12:08

    Very good post, and the same applies on the .NET side almost verbatim.

    Another error I see, sometimes, is people generating the hashcode with a formula that, depending on the values used, could generate the same hashcode int for two, different entities.

    • Brad Mongar / Dec 13 2011 12:14

      By nature the range of hashcodes is smaller than the range of data values. Any algorithm for generating hashcodes can necessarily generate hash collisions. No algorithm can avoid collisions but good algorithms evenly distribute hashes.Bad algorithms can cause hash clustering and create a great number of collisions and degrade performance.

  2. dpitt / Dec 19 2011 11:06

    Nice blog, with the popularity of distributed caches and object based (column oriented) data stores, overriding equals() seems even more risky.

  3. Amelia Hettes / Dec 22 2011 03:44

    Some truly superb posts on this blog, regards for contribution.

  4. Ruud / Dec 23 2011 04:47

    Good post. At the end you state that it is only safe to implement your own equals if the object is immutable. I would add that only some (identifying!) part of your object should be immutable. That way you can easily make a good .equals()…

  5. Joane Zermeno / Jan 8 2012 08:31

    Very interesting points you have observed, appreciate it for putting up.

Leave a reply to Brad Mongar Cancel reply