Writing Equals Methods

The equals method is usually used to denote the structural equality of two objects. That is, it does not require the objects to have the same identity, but only to be "equivalent", in some sense that depends on the domain.

A part of the contract of equals is that it should be symmetrical. However, it is easy to break this rule. Especially in a nontrivial inheritance hierarchy, it is important to test whether the arguments have the same class. For example, look at:

  class C {
    int a; int b;

public boolean equals(Object o) { if (o instanceof C) { C c2 = (C) o; return (this.a == c2.a) && (this.b == c2.b); } else { return false; } } }

class D extends C { int c;

public boolean equals(Object o) { if (o instanceof D) { D d2 = (D) o; return super.equals(o) && (this.c == d2.c); } else { return false; } } }

The code:

  C c = new C();
  D d = new D();
  // Processing
  boolean b1 = c.equals(d); // Checks whether c and d have equal values
                            // of C's instance variables.
  boolean b2 = d.equals(c); // always false.
                            // b1 and b2 can be different!

gives paradoxical results. One fix is:

  // In class C
  public boolean equals(Object o) {
    if (this.getClass() == o.getClass()) {
      C c2 = (C) o;
      return (this.a == c2.a) && (this.b == c2.b);
    } else {
      return false;
    }
  }

Another fix:

  // In class C
  public final boolean equals(Object o) {
     ...
  }

This of course means, "different" objects of D would be considered equal. And we must not forget to make GetHashCode final as well.


In a language with MultipleDispatch, equals can be implemented more naturally, without breaking symmetry. This is made possible because dispatch can occur on both arguments. For instance, in the NiceLanguage:

  class C {
    int a; int b;

equals(that@C) { return this.a == that.a && this.b == that.b; } }

class D extends C { int c;

equals(that@D) { return super && this.c == that.c; } }

The point is that the implementation is now symmetrical: equals in D will only be reached if both arguments are instances of D. As a bonus, no cast is needed.

The above code will return true if an instance of C and an instance of D are compared, and their common fields (a and b) are equal. It is equally possible to specify that equals should return false in those cases, by using the ability to define methods outside classes:

  equals(this@C, that@D) {
    return false;
  }

equals(this@D, that@C) { return false; }


CategoryPitfall


EditText of this page (last edited August 9, 2005) or FindPage with title or text search