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:
- Reflexive:
x.equals(x) == true
- Symmetric:
x.equals(y)==true && y.equals(x)==true
- Transitive:
x.equals(y)==true && y.equals(z)==true && x.equals(z)==true
- Consistent:
x.equals(y)==true && x.equals(y)==true
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:
- Use
==
to check if the argument is a reference to this object - Use the
instanceof
to check if the argument type - Cast the argument to the coreect type
- 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 byequals
-
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:
-
Compute
- Primitive field:
Type.hashCode(f)
with boxed primitive classType
- 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
- Primitive field:
-
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:
- if a > b, then b < a; if a = b, then b = a; if a < b, then b > a
- if a > b && b > c, then a > c
- 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.