Obey general contract when overriding equals

Overriding the equals method seems simple, but there are many ways to get it wrong, and consequences can be dire. The easiest way to avoid problems is not to override equals method, in which case each instance of the class is equal only to itself. This is the right to do if any of the following conditions apply:

  • Each instance of the class is inherently unique. This is true for classes as Thread that represent active entities rather than values. Equals method provided by Object has exactly the right behavior.
  • You don’t care whether the class provides a „logical equality” test. For example java.util.Random could have overridden equals method to check whether two Random instances would produce the same sequence of random numbers going forward, but designers didn’t think that clients would need or want this functionality.
  • A superclass has already overridden equals, and the superclass behavior is appropriate for this class. For example most Set implementations inherit their equals implementation from AbstractSet, List implementation from AbstractList, Map from AbstractMap.
  • The class is private or package-private, and you are certain that its quals method will never be invoked. Arguably, the equals method should be overridden under these circumstances, in case it is accidentally invoked:
@Override public boolean equals(Object o) {
    throw new AssertionError(); //Method never called
}

When it is appropriate to override Object.equals?

When a class has a notion of logical equality that differs from mere object identity, and a superclass has not already overriden equals to implement the desired behavior. This is generally the case for value classes. A value class is simply a class that represents a value, such as Integer or Date.

When you override the equals method, you must adhere to its general contract.

  • Reflexive: For any non-null reference value x, x.equals(x) must return true.
  • Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true.
  • Transitive: For any non-null reference values x, y, z if x.equals(y) returns true and y.equals(z) return true, then x.equals(z) must return true.
  • Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null references value x.equals(null) must return false.

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

The Liskov substitution principle says that any important property of type should also hold for its subtypes, so that any method written for the type should work equally well on its subtypes [Liskov87]. But imagine we are comparing a child instance that has a new property with parent.

While there is no satisfactory way to extend an instantiable class and add a value component, there is a fine workaround. „Favor composition over inheritance„. Instead of having ColorPoint extend Point, give ColorPoint a private Point field and a public view method that returns the point at the same position as this color point:

public class ColorPoint {
  private final Point point;
  private final Color color;

  public ColorPoint(int x, int y, Color color) {
    if (color == null)
      throw new NullPointerException();
    point = new Point(x, y);
    this.color = color;
  }

  /**
  * Returns the point-view of this color point
  */
  public Point asPoint() {
    return point;
  }

  @Override public boolean equals(Object o) {
    if (!o instance of ColorPoint) {
      return false
    }
    ColorPoint cp = (ColorPoint) o;
    return cp.point.equals(point) && cp.color.equals(color);
  }
}

A recipe for a high-quality equals method

  1. Use the == operator to check if the argument is a reference to this object.
  2. Use the instance of operator to check of the argument has the correct type.
  3. Cast the argument to the correct type.
  4. For each „significant” field in the class, check if that field of the . argument matches the corresponding field of this object.
  5. When you are finished writing your equals, ask yourself thee questions: Is it symmetric? Is it transitive? Is it consistent? And don’t just ask yourself, write unit tests to check that these properties are hold!
  6. Always override hashCode when you override equals
  7. Don’t try to be to clever – For example the File class should’t attempt to equate symbolic links referring to the same file.
  8. Don’t substitute another type of Object in the equals declarations
// This method does not override Object.equals
public boolean equals(MyClass o) {
...
}

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *

Acest site folosește Akismet pentru a reduce spamul. Află cum sunt procesate datele comentariilor tale.