How to avoid traps and correctly override methods from java.lang.Object

Avoid incorrect implementations and bugs by following these guidelines

All Java classes eventually have java.lang.Object, hereafter referred to simply as Object, as a base class. Because of this, all Java classes inherit methods from Object. Half of these methods are final and cannot be overridden. However, the other methods in Object can be and are overridden, often incorrectly. This article explains why it's important to implement these methods correctly and then explains how to do so.

Object declares three versions of the wait method, as well as the methods notify, notifyAll and getClass. These methods all are final and cannot be overridden. This article discusses the remaining methods that are not final and that often must be overridden:

  • clone
  • toString
  • equals
  • hashCode
  • finalize

I'll discuss the clone method first, as it provides a nice collection of subtle traps without being excessively complicated. Next, I'll consider equals and hashCode together. These are the most difficult to implement correctly. Wrapping up the article, I'll describe how to override the comparatively simple toString and finalize methods.

Why this matters

Why is it important to implement these methods correctly? In a small application written, used, and maintained by one individual, it may not be important. However, in large applications, in applications maintained by many people and in libraries intended for use by other people, failing to implement these methods correctly can result in classes that cannot be subclassed easily and that do not work as expected.

It is, for example, possible to write the clone method so that no child classes can be cloned. This will be a problem for users who want to extend the class with the improperly written clone method. For in-house development this mistake can result in excess debug time and rework when the problem is finally discovered. If the class is provided as part of a class library you sell to other programmers, you may find yourself rereleasing your library, handling excess technical support calls, and possibly losing sales as customers discover that your classes can't be extended.

Erroneous implementations of equals and hashcode can result in losing elements stored in hashtables. Incorrect implementation of these methods can also result in intermittent, data-dependent bugs as behavior changes over time. Again, this can result in excess debugging and extra software releases, technical support calls, and possibly lost sales. Implementing toString improperly is the least damaging, but can still result in loss of time, as you must debug if the name of the object is wrong.

In short, implementing these methods incorrectly can make it difficult or impossible for other programmers to subclass and use the classes with the erroneous implementation. Less serious, but still important, implementing these methods incorrectly can result in time lost to debugging.

Two themes

Two themes will reappear throughout this article. The first theme is that 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 your implementations to be correct in child classes or declare your class to be final so that there are no child classes.

The second theme is that methods have contracts -- defined behavior -- and when implementing or overriding a method, the contract should be fulfilled. The equals method of Object provides an example of a contract: the contract 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 precise class of the original object. The clone method in Object is a magic function that generates a shallow copy of the entire object being cloned.

To enable shallow cloning of your class, you implement the Cloneable interface. (For a full discussion of shallow copying versus deep copying, see the sidebar below.) Since Cloneable is a tagging interface with no methods, it's simple to implement:

public class BaseClass implements Cloneable { // Rest of the class.

// Notice that you don't even have to write the clone method! }

clone is a protected method. If you want objects from other packages to be able to call it, you must make clone public. You do this by redeclaring clone and then calling the superclass's clone method:

public class BaseClass implements Cloneable { // Rest of the class.

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 { // SomeOtherClass is just an example. It might look like // this: // // class SomeOtherClass implements Cloneable // { // public Object clone () throws CloneNotSupportedException // { // return super.clone(); // } // } // private SomeOtherClass data;

// Rest of the class.

public Object clone () throws CloneNotSupportedException { BaseClass newObject = (BaseClass)super.clone();

// At this point, newObject shares the SomeOtherClass // object referred to by this.data with the object // running clone. If you want newObject to have its own // copy of data, you must clone this data yourself.

if (this.data != null) newObject.data = (SomeOtherClass)this.data.clone();

return newObject; } }

That's it. So, what mistakes should you look out for?

1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more