This page discusses the finer points of the equals implementation suggested in "How Do I Correctly Implement the equals() Method?" (Dr. Dobb's Journal, May 2002).
Eran Tromer writes:
The code presented in the DDJ article causes test duplication. For instance, if an object is compared to itself then the full comparison test, including recursive calls to blindlyEquals of all the ancestors, is carried out twice: once in the this.blindlyEquals(o) direction and once in the o.blindlyEquals(this) direction.
Here is one solution -- in one direction it performs all tests, but in the other direction it avoids duplicating tests that were already performed. Changes from the original code are in bold.
This is always correct, and the test of each ancestor class (in the union of ancestor classes of the compared instances) is performed at most once.
Note that the above form for X.blindlyEquals() is correct if noninstances of X can never equal instances of X. Otherwise, obvious changes are needed in this specific X.blindlyEquals().
However, suppose that the above always holds. That is, for any class X that needs to overrides the equality test, instances of X can never equal noninstances of X. (Is this a reasonable assumption? I can think of counterexamples, but not natural ones). Then X may equal Y only when they share an ancestor Z such that both X and Y still use the equality test inherited from Z. Thus the following also works:
In essence, this first verifies that both instances have the same equality test (though not necessarily the same class), and then peforms this test once. The advantage is that it avoids the situation where the comparison in one direction is fully executed, even though the other direction fails trivially already in the class check.
By the way, I suspect the matter can be classified as case of double-dispatch (with certain properties), and the various solutions, especially the last one above, are reminiscent of the classical method to emulate double dispatch, as observed for instance in the Visitor design pattern
I've had an interesting exchange of letters with Rene Smit, who first wrote:
Doesn't your implementation of equals boil down to making sure the two objects are of the same class? Doesn't the following implementation work just as well?
(Several other readers had asked the same question.) Two things are different between Rene's implementation and mine. First, Rene's suggestion uses run-time type information (the getClass() method), which might not be available in all OO languages; my solution relies on instanceof only. (In particular, versions of Java prior to Java 2 did not include the getClass() method.)
Second, sometimes subclasses do not add new fields -- it only overrides methods or add new ones, for example. Using my suggested implementation of equals(), a subclass of that kind could have instances that are equal to those of the parent class; not so with Rene's code.
The first problem is a theoretical one, and does not apply to modern Java. The second problem was elegantly solved by Rene's next letter:
Then I suggest the addition of a new method called getEquivalenceClass() that returns a non-null Object representing the class. A subclass doesn't override either equals() or getEqualityClass() if it doesn't add new fields.
So, for the case that new fields are added:
And in case no new fields are added (i.e. its equivalence class is equal), simply:
Rene is correct, of course. At first I thought that there is still one case where my original solution (symmetrical equality tests) has a slight advantage: when a subclass adds a field, and assumes an implicit 'default' value for this field in instances of the superclass. For example, a ColorPoint can ensure that a Point instance equals it if they both share the same coordinates, and the ColorPoint has the black color (the implicit default). But this, too, can be done with equality classes. In Rene's own words:
You would be right if my equivalence class method is static, but it isn't. So to make it work I can write getEquivalenceClass like this:
Like your solution, it can dynamically determine how equivalent it is to Point. If the value has the default value then it can decide to make it equivalent to Point, else to ColorPoint. However, for this to work, the equals of ColorPoint should relaxed a bit and rewritten as:
In the previous version, this implementation of equals still works the same because the old equals contained an implicit test on equivalence class, which is already done in super.equals. So I think this implementation is better anyway.
By the way, for speed, if it's Java code, getEquivalenceClass can return java.lang.Class and the test can become getEquivalenceClass() == p.getEquivalenceClass().
Prof. Dr. Dominik Gruntz from the University of Applied Sciences Brugg-Windisch notes:
In order to guarantee that the equals() method is not overwritten I would recommend to declare equals() as final.
Richard Jepps notes that there are a few small errors in some of the code samples, that would prevent proper compilation. He had submitted the following complete test program, which complies and works properly:
If you have additional comments, please contact firstname.lastname@example.org.
- Tal Cohen