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.
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.
The next post is about the best practices in writing classes and interfaces.
No comments:
Post a Comment