1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
Requiring the argument to `equals` to be of a specific concrete type is
incorrect, because it precludes any subtype of this class from obeying the
[substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle).
Such code should be modified to use an `instanceof` test instead of `getClass`.
## Extending a value class to add attributes
TL;DR: use composition rather than inheritance to add fields to value types.
The most common objection to this rule arises from a scenario like the
following:
```
class Point {
double x() {...}
double y() {...}
}
class ColoredPoint {
double x() {...}
double y() {...}
Color color() {...}
}
```
It's reasonable to think first of modeling `ColoredPoint` as a subclass of
`Point`. First, because on a conceptual level, there is a clear "is-a"
relationship between the two, which we have been taught to model as inheritance.
But also because of a few concrete advantages:
1. You get the `x` and `y` fields/accessors for free.
2. You get any other methods defined on `Point` for free.
3. Users can pass a `ColoredPoint` to anything that expects a `Point`. (This is
by far the primary advantage, since the first two are just one-time-only
implementation helpers for the `Point` authors themselves.)
Although these same advantages *can* be achieved via composition, it's no longer
quite "for free"; the frequent need to call `myColoredPoint.asPoint()` is a pain
that feels unjustified, and thus subclassing is often chosen.
Unfortunately, `equals` now creates a big problem. Two `Points` should be seen
as interchangeable whenever they have the same `x` and `y` coordinates. But two
`ColoredPoints` are only equivalent if their coordinates *and* color are the
same. This, plus the general contract of `equals` (e.g. symmetry), ends up
forcing `Point(1, 2)` to respond `false` if asked whether it is equal to
`ColoredPoint(1, 2, BLUE)`.
This can be achieved by changing `equals` to be based on `getClass` instead of
`instanceof`. But do we even want to do that? Recall the main advantage we cited
for using subtyping: so that we "can pass `ColoredPoint` to anything that
expects a `Point`", we said. Yet we are in fact *not* achieving that after all.
Put simply, `ColoredPoint` is incapable of functioning properly *as a `Point`*
because it cannot participate in equality checks with other `Point`s.
The composition approach may be annoying (the frequent need for `.asPoint()`),
but aside from that annoyance, it at least achieves the three advantages
correctly.
In summation, while it is true *conceptually* that a `ColoredPoint` "is-a"
`Point`, a `ColoredPoint` is nevertheless unable to properly *function* as a
`Point`, and modeling it with subtyping is not a good idea for that reason.
## What about final or effectively-final classes?
In this case there is no disadvantage to the `getClass` trick - but there's no
great advantage to it either. Most unsafe idioms have circumstances in which
they are safe, but this doesn't change the fact that they are *generally* unsafe
and not worth propagating and legitimizing.
## More information
See [Effective Java 3rd Edition ยง10][ej3e-10] ("Obey the general contract when
overriding equals").
[ej3e-10]: https://books.google.com/books?id=BIpDDwAAQBAJ
|