This puzzle
attempts to learn from the mistakes of the previous one. Again the program
consists of a Name class and a main method that puts a name
into a hash set and checks whether the set contains the name. This time,
however, the Name class does override the hashCode method.
What does this program print?
import java.util.*;
public class Name {
private final String first, last;
public Name(String first, String last) {
this.first = first; this.last = last;
}
public boolean equals(Name n) {
return n.first.equals(first) && n.last.equals(last);
}
public int hashCode() {
return 31 * first.hashCode() + last.hashCode();
}
public static void main(String[] args) {
Set<Name> s = new HashSet<Name>();
s.add(new Name("Donald", "Duck"));
System.out.println(
s.contains(new Name("Donald", "Duck")));
}
}
Solution 58: Making a Hash of It
As in Puzzle 57, the main method of
this program creates two Name instances, both representing the same
name. This time it, happens to be Donald Duck rather than Mickey Mouse, but that
shouldn't make much difference. Again, the main method puts the first
instance into a hash set and then checks whether the set contains the second.
This time, the hashCode method is clearly correct, so it looks as
though the program ought to print TRue. Once again, appearances are
deceiving: It always prints false. What's wrong this time?
The flaw in this program is similar to the one in Puzzle 57. In that
puzzle, Name overrides the equals method but fails to override
hashCode; in this puzzle, Name overrides the hashCode
method but fails to override equals. That is not to say that
Name doesn't declare an equals method; it does, but it's the
wrong one. The Name class declares an equals method whose
argument is of type Name rather than Object. The author of
this class probably intended to override the equals method but
mistakenly overloaded it [JLS 8.4.8.1,
8.4.9].
The HashSet class uses the equals(Object)
method to test elements for equality; it is of no consequence to
HashSet that Name declares an equals(Name) method. So
where does Name get its equals(Object) method? It is inherited
from Object. This method returns true only if its argument and
the object on which it is invoked are one and the same. The main method
of our program inserts one Name instance into the hash set and tests
for the presence of another, so the test is guaranteed to return false.
To us, both instances may represent the wonderful waterfowl, but to the hash
map, they're just two unequal objects.
Fixing the program is as simple as replacing the overloaded
equals method with the overriding one found in Puzzle 57. With this equals
method, the program prints TRue as expected:
public boolean equals(Object o) {
if (!(o instanceof Name))
return false;
Name n = (Name)o;
return n.first.equals(first) && n.last.equals(last);
}
To make the program work, you merely have to add the overriding
equals method. You don't have to eliminate the overloaded one, but you
are better off without it. Overloadings represent
opportunities for error and confusion [EJ Item 26]. If compatibility
dictates that you must retain a "self-typed" equals method, implement the Object
overloading in terms of the self-typed overloading to ensure identical
behavior:
public boolean equals(Object o) {
return o instanceof Name && equals((Name) o);
}
The lesson of this puzzle is: Don't overload a method when you
want to override. To avoid unintentional overloading,
mechanically copy the declaration of each superclass method that you want to
override, or better yet, let your IDE do it for you. Besides protecting
you against unintentional overloading, this protects you against misspelling
method names. If you are using release 5.0 or a later release, apply the
@Override annotation to each method declaration that is intended to
override a superclass method:
@Override public boolean equals(Object o) { ... }
With this annotation, the program will not compile unless the
annotated method overrides a superclass method. For language designers, it is
worth considering a mandatory modifier on each method declaration that overrides
a superclass method.
No comments:
Post a Comment
Your comments are welcome!