PUBLIC OBJECT

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);
    }
  }
}`</pre>
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:
<pre class="prettyprint">`  /* 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));
  }`</pre>

### Transitivity is hard

Unfortunately, our example fails transitivity:
<pre class="prettyprint">`  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));

This is a case where a equals b and b equals c, but a doesn't equal c.

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.