next up previous
Next: Helper Methods, Packages, and Up: Basic Program Design Previous: Inheritance and the Composite

Overriding equals

A particularly important and subtle example of inheritance in Java involves the method
public boolean equals(Object o);
which is defined in the class Object, the superclass of all Java classes. Any Java class that is defined without a designated superclass is an immediate subclass of the Object class. In the class Object, equals is defined to mean Object identity. Two object references are identical if and only if they refer to exactly the same object (produced by a particular new operation).

For some classes, identity is the appropriate definition for equality, but for many others it is not. In the built-in class String, the equals method is overridden to compare the sequences of characters in strings, so that copies of the same string are considered equal. The redefinition of equals only affects the class String and any subclasses that it might have.

The overriding of the equals method is particularly delicate because the Java libraries all assume that equals defines an equivalence relation on the universal type Object, excluding the special value null, which is treated as a special case. In particular, for all non-null x and y, x.equals(y) iff y.equals(x). If the argument to equals is null, then the Java API specification stipulates that equals must return false.1.6If the class containing the overriding definition of equals can be extended (subclassed) then the coding of equals is quite subtle.

In particular, the overriding definition must confirm that the argument o belongs to exactly the same class as this. Assume that we are overriding the definition of equals in the composite class hierarchy IntList given in Section 1.7.2 above. The following code for the definition of equals in the Cons does not work in general!

  public boolean equals(Object o) {
    return (o != null) && (o instanceof Cons) &&
      (first == ((Cons)o).first) && rest.equals(((Cons)o).rest);
  }
This code can fail if Cons has a subclass ExtCons because equals can report that an instance of ExtCons is equal to an instance of Cons. Even worse, if equals is overridden in ExtCons using the same instanceof pattern,
  public boolean equals(Object o) {
    return (o != null) && (o instanceof ExtCons) &&
      (first == ((ExtCons)o).first()) && rest.equals(((ExtCons)o).rest());
  }
a.equals(b) does not imply b.equals(a) For example, if a is an instance of Cons and b is an an instance of ExtCons with exactly the same first and rest fields as a, a.equals(b) will be true while b.equals(a) will be false (because a instanceof ExtCons is false.

The problem with the instanceof pattern for writing equals is that instanceof does not test for an exact class match. We can compare the classes of objects by using the method getClass() which is inherited by all classes from Object. This method returns an instance of the built-in class Class representing the class of the receiver object. In addition, we can get the Class object for any specific class C, simply by typing C.class. Every class has exactly one Class object representing it. Hence, the equals method for Cons above can be rewritten:

  public boolean equals(Object o) {
    return (o != null) && (this.getClass() == o.getClass()) &&
      (first == ((Cons)o).first) && rest.equals(((Cons)o).rest);
  }


Finger Exercise 1.7.6.1. Load the sample program intList into the DrJava Definitions pane. Override the definition of equals for both Empty and Cons to match the definition of the equal? function in Scheme on lists of integers. The Scheme equal? function compares two lists to determine if they contain the same sequence of elements. Try evaluating a substantial set of test cases in the Interaction pane of DrJava.


next up previous
Next: Helper Methods, Packages, and Up: Basic Program Design Previous: Inheritance and the Composite
Corky Cartwright 2004-02-05