If equals() must always be overridden,
then what is a good approach for not
being cornered into having to do
object comparison?
You are mistaken. You should override equals as seldom as possible.
All this info comes from Effective Java, Second Edition (Josh Bloch). The first edition chapter on this is still available as a free download.
From Effective Java:
The easiest way to avoid problems is
not to override the equals method, in
which case each instance of the class
is equal only to itself.
The problem with arbitrarily overriding equals/hashCode is inheritance. Some equals implementations advocate testing like this in equals:
if(this.getClass() =! other.getClass()) {
return false; //inequal
}
In fact, the Eclipse (3.4) Java editor does just this when you generate the method using the source tools. According to Bloch, this is a mistake as it violates the Liskov substitution principle.
From Effective Java:
There is no way to extend an
instantiable class and add a value
component while preserving the equals
contract.
Two ways to minimize equality problems are described in the Classes and Interfaces chapter:
- Favour composition over inheritance
- Design and document for inheritance or else prohibit it
As far as I can see, the only alternative is to test equality in a form external to the class, and how that would be performed would depend on the design of the type and the context you were trying to use it in.
For example, you might define an interface that documents how it was to be compared. In the code below, Service instances might be replaced at runtime with a newer version of the same class - in which case, having different ClassLoaders, equals comparisons would always return false, so overriding equals/hashCode would be redundant.
public class Services {
private static Map<String, Service> SERVICES = new HashMap<String, Service>();
static interface Service {
/** Services with the same name are considered equivalent */
public String getName();
}
public static synchronized void installService(Service service) {
SERVICES.put(service.getName(), service);
}
public static synchronized Service lookup(String name) {
return SERVICES.get(name);
}
}
"Why would you want to compare two objects?"
The obvious example is to test if two Strings are the same (or two Files, or URIs). For example, what if you wanted to build up a set of files to parse. By definition, the set contains only unique elements. Java's Set type relies on the equals/hashCode methods to enforce uniqueness of its elements.