Thursday, November 08, 2012

Part II - Effective Java - Methods common to All Objects


This is the second in the series of posts based on the book Effective Java by Joshua Bloch. The first post was about creating and destroying objects efficiently and effectively.
In this post we will see how to write correctly some of the the standard methods that can be implemented by every object.

Obey the general contract when overriding equals

The easiest option is not to implement equals method if it is not required. Under the following conditions do not implement the equals method
1.       Each instance of the class is inherently unique
2.       You don’t create whether the class provides a “logically equality” test
3.       A super class has already overridden equals, and the behavior inherited is appropriate.
4.       The class is private or package-private and you are certain that its equals method will never be invoked.

Rules to adhere to

Override equals method if and only if you know that equals will be invoked implicitly or explicitly on the class and the default implementation will not be sufficient. Note that equals can be implicitly invoked if you look for List.contains or Map.containsKey or Map.containsValue or use it as a key in a Map or added to a Set.
If equals method is implemented it should satisfy the following conditions:
1.                   It should be reflexive: x.equals(x) must be true.
2.                   It should be symmetric: If x.equals(y) is true then y.equals(x) should also be true and vice-versa
3.                   It should be transitive: If x.equals(y) and y.equals(z) are true then x.equals(z) must also be true.
4.                   It is consistent: Multiple invocations of x.equals(y) should return the same result as long no information used in equals is modified.
5.                   For any non-null x, x.equals(null) should return false

Recipe for writing equals method

1.       Use the == operator to check if argument is a reference to this object and if so return true.
2.       Use the instanceof operator to check if argument is an instance of the current class and if not return false. Note that this also handles the situation where the argument is null as a null object when checked against instanceof any other object returns a false.
3.       Cast the argument to the correct type.
4.       For each “significant” field in the class, check to see if that field of the argument matches the corresponding field of this object. If all such fields match then return true else return false.
a.       To compare any primitive value other than float and double use the == operator
b.      To compare float use float.floatToIntBits on both the values and use the == operator
c.       To compare double use double.doublToLongBits on both the values and use the == operator
d.      To compare any object use the equals method. Take care to handle null objects. Use this technique to compare object (field == o.field) || (field != null && field.equals(o.field))
5.       Override hashCode method if equals method is overridden.
6.       Do not substitute another type for Object in the equals declaration.
The signature of the equals method is
public boolean equals(Object o) {
}
Not
public boolean equals(MyClass o) {
}
7.       For performance structure the method such that the attributes that are most likely to change are compared before the attributes that are unlikely to change.

Always override hashCode when you override equals

One must override hashCode method in every class that overrides equals. If this is not adhered to then the class will not function as per expectations when used in any hash-based collections including HashMap, HashSet and HashTable.

Rules

1.       When hashCode is invoked on the same instance of object more than once, the value returned must consistently be the same integer as long as no information used in equals comparison is modified.
2.       If two objects are equal as per the equals method then the hashCode of both the objects must return the same hashCode.
3.       It is not required that the hashcode values returned by two objects which are not equal as per equals method then be different. But the system can give a better performance if this is adhered to.

Recipe for writing hashCode method

1.       Store some non-zero value say 17 in an int variable say result
2.       For each significant field f in the object do the following:
a.       Compute an int hash code c for the field
                                                   i.      If field is boolean compute (f ? 0 : 1)
                                                 ii.      If field is byte, char, short or int, compute int(f)
                                                iii.      If field is a long, compute (int) (f ^ f >>> 32)
                                               iv.      If field is a float, compute Float.floatToIntBits(f)
                                                 v.      If field is double, compute Double.doubleToLongBits(f) and then has the resulting long as in step iii.
                                               vi.      If the field is a class field and is used for comparison then invoke the hashCode for the object. If object is null use zero. Hashcode must be recursively invoked till objects have no more children
                                              vii.      If the field is an array, then treat as if each element were a separate field.
b.      Combine the hash code c into the result as follows:
                                                   i.      result = 37 * result + c
3.       Return result
4.       Consider caching the hashCode if there is a significant cost in computing this value. But be careful to recompute it if something changes.
5.       Do not be tempted to exclude significant parts of an object from the hash code to improve performance. This will lead to clashes in Hash Maps which will result in worse performance.

Always override toString

The default implementation of toString returns the name of the object and the hashCode which will not make sense. If a good toString method is implemented then one would be able to understand what the object contains making debugging the system easier. toString should include values of all significant fields.
The general contract for toString says that the returned string should be “a concise but information representation that is easy for a person to read”.
It is advisable to document the format of the output that will be generated by the toString method.
All data that is exposed through toString should be available by their corresponding getter methods. If this is not the case then programmers will try to parse the string returned by toString and get the value which is a bad idea.

Override clone judiciously

Clone is like a constructor. The clone method should satisfy the following conditions:
1.       Implement the interface cloneable
2.       Although cloneable does not demand implementation of a clone method it is wise to provide an implementation except for trivial classes which deal with only primitive types.
3.       Consider satisfying the following rules:
a.       x.clone() != x. But this is not necessary.
b.      x.clone.getClass() == x.getClass().But this is not necessary.
c.       x.clone().equals(x). But this is not necessary.
4.       Return the object obtained by super.clone. Do not use constructor
5.       The original object should not be harmed by virtue of being cloned
6.       If required remove the final modifier of certain attributes to get a proper clone
7.       In case of Collections and Maps ensure that every object on the Collection or Map is cloned properly otherwise it will be a shallow copy and this can result in bugs.
8.       Cloneable final classes should not throw CloneNotSupportException from the clone method. This makes it easier to use the class.
9.       Classes that are extendable should implement an empty clone which throws CloneNotSupportException. This will ensure that the subclasses can by default opt out of cloning.
10.   Instead of clone method consider creating a copy Constructor or a static copy method.
public class MyClass {
    public MyClass(MyClass objectToCopyFrom) {
        //Logic to copy the incoming object.
    }
   //or
   public static MyClass getNewInstance(MyClass objectToCopyFrom) {
        //Instantiate new object copy the incoming object.
   }
}

Consider implementing Comparable

Implementing the Comparable interface helps one to leverage several generic algorithms and collection implementations that depend on this interface. sort method in the collections Collection objects like TreeSet etc. can be used.
The compareTo method should return a zero, negative or a positive integer depending on if the object is equal, less than or greater than the current object. If the object types prevent them from being compared it should throw ClassCastException.
The following rules should be followed:
1.       signOf(x.compareTo(y)) == - signOg(y.compareTo(x))
2.       x.compareTo(y) should throw an exception only if y.compareTo(x) also throws an Exception.
3.       It should be transitive. I.e. x.compareTo(y) > 0 and y.compareTo(z) > 0 then x.compareTo(z) > 0
4.       x.compareTo(y) == 0 should imply that signOf(x.compareTo(z)) == signOf(y.compareTo(z)) for all z.
5.       Ideally (x.compareTo(y) == 0) == (x.equals(y)). I.e. if compareTo indicates that the objects are equal then equals should also return true. If this is not true then this should be properly documented.

The next post is about the best practices in writing classes and interfaces.

No comments: