Methods Common to All Objects

Item10: Obey the general contract when overriding equals

It can be hard to define the concept of equal, and here are some general contracts:

  1. Reflexive: x.equals(x) == true
  2. Symmetric: x.equals(y)==true && y.equals(x)==true
  3. Transitive: x.equals(y)==true && y.equals(z)==true && x.equals(z)==true
  4. Consistent: x.equals(y)==true && x.equals(y)==true
  5. x.equals(null) == false

For Item1, it would promise itself automatically. But for Item2, things can be tricky: Say we got a class that contains a value field String, and then we would compare it with another String instance. If this comparison returns true, it would break the rule when we compare the String instance with this class instance. So when we write the equals function, we must be careful.

For Item3, it could be broken when we compare superclass instances and subclass instances together. Say, we got an instance A, B, C, among which B is the superclass of A and C. In this case, if A.equals(B) == true && B.equals(C), we may not get the result A.equals(C), because we may ommit some fields when we compare superclass instances and subclass instances, but when we comapre subclass instances separately, we would compare all the value fields, and in this way the value fields ommited in the former comparsion might cause conflicts. We may never preserve the contract while extending an instantiable class and adding a value component. Or we can break the relationship between the superclass and subclass and place the superclass in a field of the subclass.

public class ColorPoint {
  private final Point point;
  private final Color color;
  
  public Point asPoint() {
    return point;
  }
  
  @Override
  public boolean equals(Object o) {
    if(!(o instanceof ColorPoint)) {
      return false;
    }
    ColorPoint cp = (ColorPoint) o;
    return cp.point.equals(point) && cp.color.equals(color);
  }
}

For Item4, it would be safe at the most time, but if the code reles on some unreliable resources, like network, it would be violated.

As for Item5, instanceof would take care of it, so that we do not need to write other codes to check null value.

Some rules we should follow:

  1. Use == to check if the argument is a reference to this object
  2. Use the instanceof to check if the argument type
  3. Cast the argument to the coreect type
  4. Check all the significant fields in the class

Item11: Override hashCode when overriding equals

It mainly helps to make the behaviors of the object correct in HashMap and HashSet. Here are some rules:

  • hashCode must always return the same result when invoked in the same object.
  • hashCode must return the same values when two objects are equal by equals
  • hashCode is not requeired to return distinct values when invoked in two or more different objects

So here is a recommended way to compute hash code for an object:

Compute the hash code for significant fields in the object one by one:

  1. Compute

    • Primitive field: Type.hashCode(f) with boxed primitive class Type
    • Object reference: compute hash code recursively in the object, or we can cache the result
    • Array: Compute a hash code for each significant element recursively, or a constant for none-significant-element array
  2. Combine the hash code c computed above into original result:

    result = 31 * result + c

In fact, we should use something like AutoValue framework, instead of writing it by ourselves

Item12: Override toString

By doint this, we can make our log much clearer, and make the object human-readable. Also, framework should help with this, but it could be more frequent that programmers write by themselves, at least we are doing this now.

Item13: Override clone jusiciouly

We should only invoke clone on object which implements Cloneable interface, or we would get an Exception only, though there is no methods declared in this interface.

Here is some specification:

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)

Note: immutable classes should never provide a clone method.

Also we must be cautious when copying object reference, and we must avoid referring to the same object directly and simply. clone it recursively. And the Cloneable is not compatiable with final fields, unless the mutable class referrd to can be safely shared. In fact, we should remove any final modifiers to make a class cloneable.

Sometimes it is not even enough to call clone recursively, we need to use deep copy, like handling with the bucket of HashTable.

A better way to clone is to peovide a copy constructor or copy factory.

// Copy constructor
public Yum(Yum yum) { ... }

// Copy factory
public static Yum newInstance(Yum yum) { ... }

These two methods have many advantages, like properly dealing with final fields, no uncessary checked exception, as well as dealing with interfaces implemented by the class.

Item14: Consider implementing Comparable

compareTo method is not declared in Object, but in the Comparable interface. Sorting an array of objects that implement Comparable is simple:

Arrays.sort(a);

And the interface looks like this:

public interface Comparable<T> {
  int compareTo(T t);
}

There rules:

  1. if a > b, then b < a; if a = b, then b = a; if a < b, then b > a
  2. if a > b && b > c, then a > c
  3. if a == b && a > c, then b > c

Note that sometimes compareTo and equals could be inconsistent, so that it may bring some problems in Collection, Set and Map. Like in HashSet, BigDevimal("1.0") and BigDecimal("1.00") are two different elements, while in TreeSet, the two are recognized as the same emelment.

Not like equals method, it is unnecessary to type check or cast arguments in compareTo method. Because the method is statically typed.

Also, we need to invoke compareTo recursively in object reference field. And fields are compared in order. If customeried order is needed or not wiling to implement Comparable, then the Comparator is needed.

// Comparable with comparator construction methods
private static final Comparator<PhoneNumber> COMPARATOR = 
  comparingInt((PhoneNumber pn) - > pn.areaCode)
  .thenComparingInt(pn -> pn.prefix)
  .thenComparingInt(pn -> pn.lineNum);

public int compareTo(PhoneNumber pn) {
  return COMPARATOR.compare(this, pn);
}

We should use the static compare methods in boxed primitive classes, and operators < and > should be avoided.