File: EqualsGetClass.md

package info (click to toggle)
error-prone-java 2.18.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 23,204 kB
  • sloc: java: 222,992; xml: 1,319; sh: 25; makefile: 7
file content (75 lines) | stat: -rw-r--r-- 3,290 bytes parent folder | download
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