Equality is context sensitive. It’s very rarely as cut-and-dried as people think it is.
As a simple example, consider two $5 notes. I think everyone can agree that these notes have the same value – they are both worth $5. But are they equal?
No. They are two physically distinct objects. They will be in different physical condition. They have different serial numbers. Depending on the year of printing, they may have different patterns. In fact, they may even have different values, despite being the same denomination – one may be a rare note, and thus collectible. So the only context they are equal in is ‘face value’.
But what about other things with the same value? Are two $5 notes equal to one $10 note? Or ten $1 coins? Or a debit card with a $10 balance? I doubt many people would consider these to be equal, despite having the same value.
When, as a developer, we implement an equals()
method, we are choosing a default context for equality. There’s a good chance we won’t get it right for all circumstances. One of the reasons I suggest not implementing equals()
for mutable objects is that you automatically take time out of the equality context.
What should be done, instead, is that we should expose better methods that actually say what we mean. For example – the Collection
interface in Java defines equality, and does it badly. The implication is that Collections
are equal if they contain the same items – but Sets and Lists aren’t equal to each other. Lists
are equal if they contain items that are equal in the same order – but SortedSets
and LinkedHashSets
can’t be equal to a List
even if they have the same items in the same order. But a LinkedList
and an ArrayList
must be equal – because a list is a list is a list, right? Perhaps the Collection
interface needs a containsSame()
method to complement the containsAll()
– and collections where order is important could implement an interface that provides containsSameInSameOrder()
? This would probably be better than an equals()
method.
(While we’re on the Collections API: note that collections that provide sort functions can all take custom Comparators
– because sorting is context sensitive. Yet no collection accepts a custom equality-checker; if you want to provide a custom context for equality, you need to build your own collection class, or decorate incoming objects)
Create fine-grained immutable objects, and implement equality checks there. Coarse-grained objects – even if they are immutable – probably shouldn’t bother with equality checks, because there will be too many contexts for “equality” to be consistently defined. And mutable objects shouldn’t bother, because you automatically leave out the temporal context.