Methods Common to All Objects
Obey the general contract when overriding equals()
The == operator tests for instance equality;
whereas the equals() method may be overriden to test for value equality.
But by default it is the same as ==
When to use default impl of equals()
Each instance of the class is inherently unique.
There is no need for the class to provide a “logical equality” test.
no need for two equivalent regexes to compare equal if this functionality is not needed by client code
A superclass has already overridden equals, and the superclass behavior is appropriate for this class.
The class is private or package-private, and you are certain that its equals() method will never be invoked.
There is usually no need to explicitly test for null in the equals() method
because the standard implementation requires an instanceof() check, which is specified to return false if null is provided.
Contract
violation of this contract means it is uncertain how other objects will behave when confronted with our object
(reflexivity) x.equals(x) == true, for x != null
hard to violate unintentionally
It is generally hard to fulfil the requirement of symmetry when creating a subclass with an equals() method that is interoperable with the super class.
subclass.equals(superclass) and superclass.equals(subclass) must be equal, but you may not always be able to change the implementation of the equals() method in the super class
transitivity requires that: if a.equals(b) and b.equals(c), then a.equals(c) must be true.
no way to extend an instantiable class and add a value component while preserving the equals contract.
This is because for class_with_added_value.equals(superclass) to be symmetrical to superclass.equals(class_with_added_value), the equals() method in the subclass must ignore the additional value component when the superclass passed as the argument. However, when comparing one instance of the subclass with another instance of the subclass, the equals() method will also compare the additional value component. As a result, when class_with_added_value_1.equals(superclass) and class_with_added_value_2.equals(superclass) are true, class_with_added_value_1.equals(class_with_added_value_2) may not be true.
(consistency) x.equals(y) == x.equals(y), for x, y != null
Do not write an equals() method that depends on unreliable resources
e.g., IP addresses as opposed to URLs, because DNS look-up may fail
(non-nullity) x.equals(null) == false, for x != null
hard to violate unintentionally, unless an exception is thrown instead of returning false
How to implement
The general steps to implement an equals() method is as follows:
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.
When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?
When an object has a canonical form, and the equality comparisons of fields are complex, consider storing a canonical form of the object to be used in the equals() method.
Note that this canonical form would have to be updated if the object is mutable.
Performance of the equals() method depends on the order of the fields being compared. If possible, try to compare fields that are most likely to be different first.
Use automatic generation of equals() and hashcode() methods provided by @AutoValue annotation.
Comparing types
For primitive fields whose type is not float or double, use the == operator for comparisons
for object reference fields, call the equals() method recursively
for float fields, use the static Float.compare(float, float) method
for double fields, use Double.compare(double, double).
For array fields, apply these guidelines to each element.
If every element in an array field is significant, use one of the Arrays.equals() methods.
Some object reference fields may legitimately contain null.
To avoid NullPointerException, check such fields for equality using the static method Objects.equals(Object, Object).
Always override hashCode() when overriding equals()
violating this prevents proper functioning of hash-based collections (ie hashset, hashmaps)
Consider lazy initialization if calculation of hashcode is resource intensive and not always required.
worst possible hash-code implementation is returning the same number
degrades performance horrifically
rely on the Object.hashCode(...) to compute the hash-code unless performance is very important
Contract
(consistency) x.hashCode() == x.hashCode(), for x != null
(equality) x.equals(y) => (x.hashCode() == y.hashCode()), for x, y != null
if a.equals(b) is false then a.hashCode() doesn't have to be different of b.hashCode()
Always override toString()
unless a superclass already did it
Provide information of all relevant members of the object in the toString() output.
It needs to be a full representation of the object and every information contained in the toString representation should be accessible in some other way in order to avoid programmers to parse the String representation.
makes systems using the class easier to debug
disadvantage: once specified, it’s for-life, users will rely on it
don’t override toString() on
static utility class
enum types
rely on IDE to create a good toString() implementation
Override clone() judiciously
Avoid implementing Cloneable if possible as it a fragile language feature, requiring many extralinguistic factors. It requires the class and all of its superclasses obey a complex, unenforceable, thinly documented protocol.
A better approach to object copying is to provide a copy constructor or copy factory.
impl
All classes that implement Cloneable should override clone with a public method whose return type is the class itself. This method should first call super.clone, then fix any fields that need fixing. Typically, this means copying any mutable objects that comprise the internal "deep structure" of the object and replacing the clone's references to these objects with references to their copies.
The only exception is arrays which should generally be copied using the clone() method. Note: clone() still doesn't copy arrays of 2 or more dimensions deeply.
Consider implementing Comparable
implementing Comparable, class indicates its instances follow natural ordering
This is fixed for those objects
whenever a value class has sensible ordering, Comparable should be implemented
By implementing Comparable interface, you allow the class to interoperate with all of the generic algorithms and collections that depend on this interface. ie treeset
there is no way to extend an instantiable class with a new value component while preserving the compareTo() contract, unless you are willing to forgo the benefits of object-oriented abstraction.
to preserve the compareTo() contract in a subclass, the compareTo() must be implemented in a way such that objects can only ever be compared to other objects of the exact same class, hence violating Liskov substitution principle.
results returned by equals() and compareTo() should generally be consistent;
If a class has multiple significant fields, the order in which you compare them is critical.
Start with the most significant field and work your way down.
Do not use comparison that rely on the fact that the difference between two values is negative if the first value is less than the second, zero if the two values are equal, and positive if the first value is greater:
e.g., return instance_a.value - instance_b.value.
This approach suffers from the dangers relating to integer overflows and artifacts from floating point arithmetic.
Use the static compare method instead: e.g., Integer.compare(instance_a.value, instance_b.value).
static compare() methods to primitive, as have been added to all boxed primitive classes, instead of < and >
do not use hashCode arithmetics-based comparisons
though somewhat more performant, they are fraught with danger from integer overflow and FP arithmetic artifacts
Contract
(reflexivity) (x.compareTo(y) == 0) => (sgn(x.compareTo(z) == sgn(y.compareTo(z)), for x, y, z != null
(symmetry) sgn(x.compareTo(y)) == -sgn(y.compareTo(x)), for x, y != null
helps imposing total order
(transitivity) (x.compareTo(y) && y.compareTo(z)) > 0 => (x.compareTo(z)) > 0, for x, y, z != null
helps imposing total order
(optional consistency with equals) (x.compareTo(y) == 0) <=> (x.equals(y)), for x, y != null
if violated, still will yield valid comparisons but will generally not guarantee obeying the general contract for collections and maps
examples: java.util.BigDecimal in HashSet and TreeSet
BigDecimal("1.0") and BigDecimal("1.00") compares unequal when using the equals() method but compares equal when using the compareTo() method. As a result, after inserting both into a TreeSet, there will only be one element. Whereas after inserting both into a Hashset, there will be two elements. This is because TreeSet and HashSet uses compareTo() and equals() respectively for comparison.
Last updated
Was this helpful?