original article: https://www.artima.com/lejava/articles/equality.html

In Item 8 of Effective Java, Josh Bloch describes the difficulty of preserving the equals contract when subclassing as a “fundamental problem of equivalence relations in object-oriented languages.” Bloch writes:

There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.

Chapter 28 of Programming in Scala shows an approach that allows subclasses to extend an instantiable class, add a value component, and nevertheless preserve the equals contract. Although in the technique is described in the book in the context of defining Scala classes, it is also applicable to classes defined in Java. In this article, we present the technique using text adapted from the relevant section of Programming in Scala, but with the code examples translated from Scala into Java.

Common Equality Pitfalls

Class java.lang.Object defines an equals method, which subclasses may override. Unfortunately, it turns out that writing a correct equality method is surprisingly difficult in object-oriented languages. In fact, after studying a large body of Java code, the authors of a 2007 paper concluded that almost all implementations of equals methods are faulty.

This is problematic, because equality is at the basis of many other things. For one, a faulty equality method for a type C might mean that you cannot reliably put an object of type C in a collection. You might have two elements elem1elem2 of type C that are equal, i.e., “elem1.equals(elem2)” yields true. Nevertheless, with commonly occurring faulty implementations of the equals method, you could still see behavior like the following:
Set<C> hashSet = new java.util.HashSet<C>();
hashSet.add(elem1);
hashSet.contains(elem2); // returns false!

Here are four common pitfalls that can cause inconsistent behavior when overriding equals: