Methods Common to All Objects

Photo by Ilya Pavlov on Unsplash

Methods Common to All Objects

·

6 min read

Object is a concrete class, designed primarily for extension. This article would tell you when and how to override the methods.

Item 10: Obey the general contract when overriding equals

The default behaviour allows an instance to be only equal to itself. It need not be overridden if:

  1. Each instance of class is inherently unique
  2. There is no need for the class to provide the logical equality test (Java Pattern doesn't override default equals())
  3. The superclass has overridden equals() and it is sufficient
  4. Class is private or package-private and not called/used

The overriding is required generally in case of logical equality which is different from object identity and superclass hasn't overridden the equals().

Logical equality is usually used in value classes like String, Integer or their composite version etc where usually we are interested that two values are logically equal instead of being identical. Enums don't required overriding as they needs object identity not logical reference.

Requirements for overriding equals() as per Specification is that it should be equivalence relation.

Equivalence relation has properties:

  1. Reflexive
  2. Symmetric
  3. Transitivity
  4. Consistent

Some cautious notes:

  1. Once equals() contract is violated, the behaviour of Object equals() usage can be non-deterministic
  2. There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you violate OOP (LSP Principle, use composition instead)
  3. Don't write equals() method relying on unreliable resources

Java Timestamp class extends Date and breaks symmetry property.

objA instanceof objB is returns false if objA (can be null) is not type of objB. Use it instead of additionally null checking objA.

Recipe for writing equals() method:

  1. Use the == operator to check if the argument is a reference to this object.
  2. Use the instanceof operator to check if 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. After writing make sure it is: symmetric, transitive, consistent

Item 11: Always override hashCode when you override equals

We must override hashCode in every class that override equals()

The hashCode() contract:

  1. Return value is consistent in an execution, given the fields in equals() are not modified
  2. Equal objects (via equals()) must have same hashCode
  3. Unequal objects may have same hashCode

If we don't override the hashCode() the second point in contract fails.

The specification of hashCode() shouldn't be shown in detail so that its implementation can be flexible and clients don't highly couple their code with it. String and Integer exposes it and can't be changed in future release for backward compatibility. The fields which are excluded by equals() must be excluded in hashCode() as well to follow second point of contract.

Item 12: Always override toString

Providing a good toString() implementation makes the class much more pleasant to use and makes systems using the class easier to debug. If we don't it would print ClassName@hashCode which is not descriptive enough.

Class must clearly document what it intends to show in toString() and if possible hint at the scope of change for flexibility. It would discourage client to parse the toString() thus avoid the highly coupled client code. Public methods to access fields would discourage clients to parse toString().

Item 13: Override clone judiciously

Cloneable interface was intended as a mixin interface (Item 20) to show that class permits cloning, but it fails to serve the purpose

  1. The Cloneable interface doesn't have a clone() method and the one in Object is protected. In practice a class implementing Cloneable is expected to provide a properly functioning public clone method.
  2. Immutable classes should never provide a clone() method.
  3. The clone() method functions as a constructor; we must ensure that it does no harm to the original object and that it properly establishes invariants on the clone. This can be ensured if there is not shared state between the original and newly created object.
  4. The Cloneable architecture is incompatible with normal use of final fields referring to mutable objects.
  5. It is better to use copy constructor or copy factory. All general purpose Collections provide a copy constructor.
  6. It should be avoided except for special cases (Item 67) and arrays are copied better using clone().

Item 14: Consider implementing Comparable

By implementing Comparable interface we allow our classes to interoperate with many algorithms and collections.

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

Here the parameter is same as class type unlike equals() where general Object is passed.

The return values are:

  • -1 (this < t)
  • 0 ( this == t)
  • 1 (this > t)

where this is the current instance and t is another instance to which the current instance is compared. The relation operators are just to quickly explain the relation and does not correspond to actual operators. The decision has to be taken by the implementor what field values he chooses in what order to return a specific value. Readers might notice that the output is same as signum function.

Some points to remember while implementing compareTo():

  1. sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.
  2. should be transitive
  3. (x.compareTo(y) == 0) == (x.equals(y)) , else natural ordering is inconsistent with equals()

Classes that use compareTo() are generally the ones related to sorting like TreeSet and TreeMap.

The above points make the compareTo() follow reflexivity, symmetry and transitivity. Thus they suffer from same caveat we can't extend an instantiable class with a new value component while preserving the compareTo() contract, unless we forgo the Object-oriented benefits.

As per the 3rd point if compareTo() and equals() are inconsistent then it would still work but the sorted collections (like TreeSet, TreeMap) may not obey the general contract of collection interfaces (Collections, Set, Map). It is because the sorted collection uses compareTo() instead of equals() for equality, thus it has to be kept in mind. A noticeable example of this is the BigDecimal class whose compareTo() and equals() are inconsistent. Thus "1.00" and "1.0" value of BigDecimal instance are equal as per compareTo() but different from perspective of equals().

Implementing compareTo() is similar to equals() however because of static typing instanceof would not be required. It should throw NullPointerException if parameter is null.

Relational operators < and > should not be used in compareTo() as they are verbose and error-prone. Use public static compareTo() methods of value classes.

From Java 8 onwards comparator construction methods can also be used which are more readable. Prefer using Java static import for readability. It contains a lot of functions like comparingInt(), thenComparingInt() and similar methods for other value classes.

This is it!