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 redefined 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. This selective form of method redefinition is called method overriding.
The overriding of the equals method is particularly delicate because the Java libraries all assume that equals defines an equivalence relation-except on the argument 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.5If 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.5.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 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) && (o.getClass() == Cons.class) && (first == ((Cons)o).first) && rest.equals(((Cons)o).rest); }
Finger Exercise 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 mathematical notion of equality on lists of integers, i.e.,
two lists are equal iff they contain exactly the same elements
in exactly the same order.
Try evaluating a substantial set of test cases for your new method
in the Interaction pane of DrJava.