Correctly override Object methods.
Object declares three versions of the wait method, as well as the methods notify,
notifyAll and getClass. These methods all are final and therefore cannot be
overridden. This article discusses how to override the non-final methods:
clone
toString
equals
hashCode
finalize
Two themes
1. You must pay attention to whether your implementations of these methods will
continue to be correct in child classes. If not, you should either rewrite them or
declare your class to be final.
2. Methods have contracts = specifications. The equals method of Object has a
contract that states that if the parameter to equals is null, then equals must return
false. When overriding equals, you are responsible for ensuring that all the specifics
of the contract are still met.
Implementing clone
The clone method allows clients to obtain a copy of a given object without knowing the
class of the original object. The clone method in Object generates a shallow copy of the
entire object being cloned. The following is pseudocode for it.
protected Object clone() throws CloneNotSupportedException
{ if (!(this instanceof Cloneable))
throw new CloneNotSupportedException();
Object klone = new ChunkOfMemoryOfTheSameSizeAs(this);
>
return klone;
}
To enable shallow cloning, implement the Cloneable interface. Since Cloneable is a tag
interface, this is trivial:
public class BaseClass implements Cloneable
{ // That's it! You don't even have to write the clone method.
}
However, clone is a protected method. If you want objects from other packages to be
able to call it, you must make it public: redeclare clone and call the superclass's clone
method:
1
public class BaseClass implements Cloneable
{ public Object clone() throws CloneNotSupportedException
{ return super.clone();
}
}
Finally, if you want some of the member data in the class to be copied deeply, you must
copy these members yourself:
public class BaseClass implements Cloneable
{ private X data;
public Object clone () throws CloneNotSupportedException
{ BaseClass newObject = (BaseClass)super.clone();
// Now, newObject shares the X object referred to by this.data
// To have its own copy of data, you must clone it:
if (this.data != null)
newObject.data = (X) this.data.clone();
return newObject;
}
}
Mistakes look out for:
1. Implement the Cloneable interface if you want your class to be cloneable!
Object.clone checks that the Cloneable interface has been implemented. If not,
it throws a CloneNotSupportedException.
2. Don't implement clone by using a constructor. The doc. states that clone:
Creates a new object of the same class as this object. It then initializes
each of the new object's fields by assigning it the same value as the
corresponding field in this object. No constructor is called.
Avoid implementing clone as follows:
public class BaseClass implements Cloneable
{ public Object clone () throws CloneNotSupportedException
{ return new BaseClass (this);
}
}
In other words: what was correct in C++ is incorrect in Java. There are two
reasons to avoid such an approach: first, the contract for clone states that no
constructor is called. Second, and more importantly, child classes could now
return the wrong type from clone. In the example below, the object returned by
clone is a BaseClass, not a ChildClass!
public class ChildClass extends BaseClass
{ // Use clone from BaseClass
}
2
3. Avoid using constructors to copy subobjects. Another mistake is to use
constructors to copy subobjects when implementing clone. Consider the
following example class, which uses Dimension as the subobject:
import java.awt.Dimension;
public class Example implements Cloneable
{ private Dimension dim;
public Object clone() throws CloneNotSupportedException
{ Example newObject = (Example)super.clone();
// Notice the use of a constructor below instead of
// a clone method call. If you have a subclass of
// Dimension, any data in the subclass (e.g. a third
// dimension value like z) will be lost.
if (this.dim != null)
newObject.dim = new Dimension(dim); //slice?
return newObject;
}
}
The preferred way to write this clone method would be:
public class Example implements Cloneable
{ private Dimension dim;
public Object clone () throws CloneNotSupportedException
{ Example newObject = (Example)super.clone();
if (this.dim != null)
newObject.dim = (Dimension) this.dim.clone();
return newObject;
}
}
Summary: Make copies of member variables using their clone methods.
4. Pay attention to synchronization on the clone method. In a multithreaded
environment you want to synchronize clone so that the underlying object stays
internally consistent while being copied. This is different from a constructor,
which almost never needs synchronization.
5. Sometimes you should treat clone like a constructor. If you do something
special in each constructor, like incrementing an "objects created" count, you
probably want to do the same thing in the clone method.
6. Classes used by others should usually implement clone. This is most important
when the class is part of a class library used by others who don't have access to
the source code. Failing to implement the clone method can cause problems for
clients attempting to write their own clone methods..
3
If you're not producing a third-party library, waiting to implement clone until it's
needed is reasonable. This is especially true because once you've overridden
clone, you must pay attention to overriding clone in all the child classes.
7. Child classes must pay attention to clone methods inherited from parent
classes. Well-written third-party library classes will often implement clone.
However, once a class becomes cloneable, that class's children become cloneable,
too. If you extend a class that is cloneable, you must consider whether the clone
method you inherit (which will make a shallow copy of all of the data in your
subclass) does what you want it to.
8. Should you continue to have the exception specification "throws
CloneNotSupportedException"? Having this specification makes the client's
call to clone more awkward: they must be prepared to catch the exception. On the
other hand, if you omit this exception, then subclasses must supply a clone
operation: they are unable to retract from the contract. If the class is in a hierarchy
design to support cloning, you may reasonably omit the exception.
A hierarchy built for cloning.
Cloneable
public, no
exception
IBase specification
public clone()
"first" class
CW
data
no new data public clone() Immutable
means no data means no
override need to
override
CX CY
s : String
"second" class
CZ
data
public clone()
4
Derive the base interface from Cloneable and set up clone() correctly:
public interface IBase extends Cloneable
{ public Object clone();
. . .
}
"First" class's clone:
public Object clone()
{ CW copy = null;
try
{ copy = (CW) super.clone(); //Object.clone
}
catch(CloneNotSupportedException exc)
{ throw new InternalError(exc.getMessage());
} // ^^ an unchecked exception
>
return copy;
}
Because Object.clone may throw CloneNotSupportedException, we must either
catch it or propagate it.
"Second" class's clone
public Object clone()
{ CZ copy = (CZ) super.clone(); //CX.clone
>
return copy;
}
Because CX.clone cannot throw CloneNotSupportedException, no try block!
Immutable object data (ex: String, Color, Font) should be shared, not cloned
(these classes don't have a public clone, anyway).
//Example of a deep clone with a collection as a component
public class Third extends CZ
{ private ArrayList m_comps; //elements have type IBase
public Object clone()
{ Third klone = (Third) super.clone();
klone.m_comps = (ArrayList) m_comps.clone();
for(int i=0, ub = m_comps.size(); i
klone.m_comps.set(i,((IBase)m_comps.get(i)).clone());
return klone;
}
}
5
Implementing clone when you want to update final instance data
Is problematic! If you try to update final fields in your clone method, you are out of luck
– final fields can only be assigned in constructors. I suggest giving up! Write a C++ style
clone that simply returns a new copy, using the copy constructor. Note that this situation
is the case whenever you are trying to clone instances of non-static inner classes: the
parent pointer is final.
Implementing equals and hashCode
Because of their contracts, if you override one, you must override the other method as
well. Here are the important requirements, as documented for Object:
1. The hashCode method must return the same integer value every time it is
invoked. The documentation does allow the hashCode value to change if the
value of the equals method changes (the state changes).
2. If x.equals(y), they must return the same hashCode.
3. The equals is reflexive: x.equals(x) should return true.
4. The equals is symmetric: If x.equals(y), then y.equals(x).
5. The equals is transitive: If x.equals(y) and y.equals(z), then x.equals(z).
6. Finally, x.equals(null) should be false.
Object provides a simple implementation of equals. It tests the objects for referential
equality: does x equal y? Some of the standard Java classes override this to provide a
more useful notion of equality -- usually content equality. The equals implementation of
String, for example, returns true if the two objects are both String objects containing
exactly the same characters in exactly the same order. The equals method of Dimension
returns true if the passed-in object is a dimension with the same width and height as the
Dimension object executing the equals method.
The default implementation of hashCode provided by Object returns something
corresponding to the object's address in memory or location in the Java virtual machine's
global object array. Again, some of the standard Java classes override this method.
String, for example, overrides the hashCode implementation in Object to return a hash
of some or all of the characters making up the String. This allows two String objects
with the same characters in the same order to return the same hash value.
Here is Dimension’s implementation of equals. Spot the error.
public boolean equals(Object obj)
{ if (obj instanceof Dimension)
{ Dimension d = (Dimension)obj;
return (width == d.width) && (height == d.height);
}
return false;
}
6
What’s wrong? Using instanceof creates problems of its own, if the class is not final.
Consider a derived class: ThreeDeeDim. Objects this type should be equal only if they
have equal height, width and depth. ThreeDeeDim might look like this:
public class ThreeDeeDim extends Dimension
{ public int depth;
public ThreeDeeDim (int width, int height, int depth)
{ super (width, height);
this.depth = depth;
}
public boolean equals (Object o)
{ if (super.equals(o)&& o.getClass().equals(this.getClass()))
return ((ThreeDeeDim)o).width == this.width;
else
return false;
}
}
Unfortunately, this implementation of equals doesn't meet the symmetry requirement
listed above. (Can you see why?) I could fix this problem by rewriting the equals
method of Dimension:
public boolean equals(Object obj)
{ if (obj != null && obj.getClass().equals(this.getClass()))
{ Dimension d = (Dimension)obj;
return (width == d.width) && (height == d.height);
}
return false;
}
Now, objects of type ThreeDeeDim won't return true when compared to objects of type
Dimension.
The key points to remember when implementing equals and hashCode:
There are many details specified in each method's contract.
You must implement these two methods together.
Java 2 allows the value returned by hashCode to change if the underlying data
changes, but you should be wary of doing this because data can then be stranded
in hash tables. (On the other hand, if you mutate an object being used as a hash
table key, you deserve your mistake!)
You must pay attention to inheritance, especially when implementing equals.
This means comparing classes with getClass rather than with instanceof.
Once a class has overridden equals and hashCode, the child classes may also
require their own implementations.
7
Implementing toString
The toString method is the easiest of all the methods in Object to override correctly.
This is because the contract is so loosely defined. The javadoc for toString reads:
[toString] returns a string representation of the object. In general, the toString
method returns a string that "textually represents" this object. The result should
be a concise but informative representation that is easy for a person to read. It is
recommended that all subclasses override this method.
The toString method for class Object returns a string consisting of the name of
the class of which the object is an instance, the at-sign character `@', and the
unsigned hexadecimal representation of the hash code of the object.
None of these requirements are as precise as the requirements for clone, hashCode or
equals. Nevertheless, it is still possible to implement this method somewhat incorrectly:
public class BaseClass
{ private int x;
public String toString()
{ return "BaseClass[" + x + "]"; // Not quite correct.
}
}
Calling toString on objects of this class will result in output that looks something like:
BaseClass[4]
The problem is that someone might extend BaseClass and but not override toString:
public class Extension extends BaseClass
{ /* no override of toString }
Now, calling toString on objects of class Extension results in output like:
BaseClass[4]
The class name reported by toString is wrong! The object is an Extension object and
the toString method is reporting it as a BaseClass object. You could blame the
Extension class for not implementing toString itself, but the contract for toString
only recommends that child classes implement their own version.. Instead, you should
write toString in BaseClass so that it behaves correctly in child classes. You can do
this by writing the toString implementation like this:
public String toString()
{ // This implementation behaves properly in child classes.
return getClass().getName() + "[" + x + "]";
}
8
Implementing finalize
There are only three simple things to remember if you override finalize. First, you
should call the finalize method of the parent class in case it has cleanup to do.
protected void finalize() throws Throwable
{ super.finalize(); }
Second, you should not depend on the finalize method being called. There is no
guarantee of when (or if) objects will get garbage collected and thus no guarantee that the
finalize method will be called before the running program exits. Finally, remember that
code in finalize might fail and throw exceptions. You may want to catch these so the
finalize method can continue.
protected void finalize() throws Throwable
{ try
{ // Do stuff here to clean up your object.
}
catch (Throwable t) {}
super.finalize();
}
In general, finalize doesn't need to be overridden – don't bother, in most cases.
One place where you may want to bother is if a class acquires a resource like a file
descriptor that needs to be released. This is what destructors are for in C++! In Java, the
best you can do is supply a close() method, say, and try to release the resource in
finalize(), if the user forgot to call close() explicitly:
public class InputFile
{ public class InputFile(. . .) throws IOException
{ /* open file */ }
public void close() throws IOException
{ m_in.close();
m_in = null;
}
protected void finalize() throws Throwable
{ try
{ if (m_in != null)
m_in.close();
}
catch(IOException e){}
super.finalize();
}
private InputStream m_in;
}
9
Conclusion
There are traps to overriding all of the non-final methods inherited from Object. Some of
them are subtle enough that even classes provided in the core Java libraries get them
wrong. Nevertheless, with a bit of care, you can implement the methods correctly. And
won’t you be proud of yourself!
When building large products and when constructing third-party class libraries, it is
important to take the care needed to get these implementations right. After all, some
developer might read the documentation and assume your code does what the
documentation says! For smaller projects, it can sometimes be reasonable to meet most,
but not all, of the requirements for these methods. In those cases you should at least make
your decision consciously and document it.
10