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.