Implementing equals() is hard
When modeling an entity that may or may-not have an id assigned, writing the equals method is surprisingly hard. What to do when there's no id? Falling back on equals-by-value is tempting:public class Product {
  private final Id id;
  private final String description;
  private final Money price;
  ...
  public boolean equals(Object other) {
    if (!(other instanceof Product)) {
      return false;
    }
    Product otherProduct = (Product) other;
    if (id != null && otherProduct.id != null) {
      return id.equals(otherProduct.id);
    } else {
      return description.equals(otherProduct.description)
          && price.equals(otherProduct.price);
    }
  }
}Unfortunately, this doesn't work. The
equals() method must be reflective, symmetric and transitive. For arbitrary values of a, b, and c, the following tests* must always pass:  /* reflective */
  assertTrue(a.equals(a));
  /* symmetric */
  assertTrue(a.equals(b) == b.equals(a));
  /* transitive */
  if (a.equals(b) && b.equals(c)) {
    assertTrue(a.equals(c));
  }Transitivity is hard
Unfortunately, our example fails transitivity:
  Product a = new Product(5, "Large Pepperoni", Money.usd(11));
  Product b = new Product(null, "Large Pepperoni", Money.usd(11));
  Product c = new Product(6, "Large Pepperoni", Money.usd(11));Consequences
Without a proper equals method, it's dangerous to use this Product class in Collections. For example, the behaviour of
HashMap.remove(b) is poorly defined for a HashMap that contains both a and c.The Fix
The equals method should be defined by value or by id, but not either.



