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:
- Each instance of class is inherently unique
- There is no need for the class to provide the logical equality test (Java Pattern doesn't override default
equals()
) - The superclass has overridden
equals()
and it is sufficient - 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:
- Reflexive
- Symmetric
- Transitivity
- Consistent
Some cautious notes:
- Once
equals()
contract is violated, the behaviour of Objectequals()
usage can be non-deterministic - 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)
- 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:
- Use the
==
operator to check if the argument is a reference to this object. - Use the
instanceof
operator to check if the argument has the correct type. - Cast the argument to the correct type.
- For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object.
- 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:
- Return value is consistent in an execution, given the fields in
equals()
are not modified - Equal objects (via
equals()
) must have same hashCode - 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
- The
Cloneable
interface doesn't have aclone()
method and the one in Object is protected. In practice a class implementingCloneable
is expected to provide a properly functioningpublic
clone method. - Immutable classes should never provide a
clone()
method. - 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. - The
Cloneable
architecture is incompatible with normal use offinal
fields referring to mutable objects. - It is better to use copy constructor or copy factory. All general purpose
Collections
provide a copy constructor. - 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()
:
- sgn(
x.compareTo(y)
) == -sgn(y.compareTo(x)
) for all x and y. - should be transitive
(x.compareTo(y) == 0) == (x.equals(y))
, else natural ordering is inconsistent withequals()
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!