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 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 extends Foo>.
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.