Java dilemmas

§    Overriding equals

An extended class cannot use any new state in the implementation of the equals method.

Let us say you have a Point class that contains an x and a y coordinate as integers. That class overrides equals and hashCode.

You then decide to derive a PrettyPoint extends Point that adds a color. Should the PrettyPoint override the equals to consider the new color?

A PrettyPoint may be used anywhere that a Point is expected. A plain Point may be compared to an extended PrettyPoint.

Because of commutativity, we must guarantee the same result is returned from plainPoint.equals(prettyPoint) and prettyPoint.equals(plainPoint). The first method is not overridden and does not know about color. So a PrettyPoint clearly must ignore color when compared to a plain Point.

Can a PrettyPoint use the color when compared to another PrettyPoint? Transitivity requires that if point1.equals(point2) and point2.equals(point3), then point1.equals(point3). If point2 is a plain Point, then it does not matter if point1 and point3 are instances of PrettyPoint. The point1.equals(point3) must ignore the color to avoid breaking transitivity.

There are a couple ways out of this dilemma.

We could make Point extend PrettyPoint and make the extended Point colorless. The resulting hierarchies are unattractive and feel upside down. Each time we add a class with additional state, we must change all derived classes. This approach also does not work for a client attempting to extend third party code.

Another approach is to make a PrettyPoint contain a Point with a method for getting that contained Point when necessary. This is best when all state is immutable.

If Point is an interface rather than a concrete class, then each implementation of Point can insist that only objects from the same implementation be equal. Commutativity and associativity are preserved. Utility classes can define specific types of equality through the public methods of interfaces.

The situation is the same for objects implementing Comparable.

November, 2009

§    Arrays

Arrays do not guarantee runtime type-safety. For example, the following code compiles without warnings, even with -Xlint:all.
String[] strings = new String[1];
Object[] stuff = strings;
stuff[0] = new Integer(13);

There are no explicit casts, yet the code generates a java.lang.ArrayStoreException.

This is the best reason for Java compiler to prohibit the creation of arrays of generic objects. All guarantees of type safety would be lost.

This is also the reason that Java makes a distinction between a List and a List.

If we allowed a List to be passed as a List then the compiler would have no way to prevent a Double being added to the latter.

November, 2009

Bill Harlan.

Return to parent directory.