« | July 2025 | » | 日 | 一 | 二 | 三 | 四 | 五 | 六 | | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | | | |
| 公告 |
关注电子政务、大型企业应用开发、Web、Workflow、MOM、MDA、RCP、GEF email:gmluyang@gmail.com
|
Blog信息 |
blog名称:SixSun的Blog 日志总数:152 评论数量:372 留言数量:13 访问次数:2372838 建立时间:2004年12月13日 |

| |
[J2SE / 基础类]How to avoid traps and correctly override methods from java.lang.Object 文章收藏
SixSun 发表于 2005/10/20 15:36:12 |
How to avoid traps and correctly override methods from java.lang.Object
Avoid incorrect implementations and bugs by following these guidelines
Summary-->
SummaryThe base class for all Java classes, java.lang.Object allows five of its methods to be overridden by subclasses. Sometimes it is necessary to override the default implementations provided by Object. Unfortunately, it's easy to override these methods incorrectly, which can result in classes that are difficult to subclass and that fail, in subtle ways, to work properly. This article explains why it's important to implement these methods correctly and also shows how to override these methods and get it right. (3,500 words) -->By Mark Roulo
500)this.width=500'>500)this.width=500'> Printer-friendly version | 500)this.width=500'> Mail this to a friend
Page 1 of 3
Advertisement
500)this.width=500'>ll 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?
Don't fail to implement the Cloneable interface if you want your class to be cloneable. The clone method from Object checks that the Cloneable interface has been implemented. If the Cloneable interface hasn't been implemented, a CloneNotSupportedException is thrown when clone is called.
Don't implement clone by using a constructor. The javadoc for the clone method states that it:
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.
Notice that "no constructor is called." Avoid implementing clone as follows:
public class BaseClass implements Cloneable { public BaseClass (/* parameters */) { // Code goes here... } // Rest of the class. public Object clone () throws CloneNotSupportedException { return new BaseClass (/* parameters */); } }
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 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 }
Further, the child class cannot override clone to make a deep copy of the member variables in the ChildClass. The following code demonstrates this problem:
public class ChildClass extends BaseClass { private SomeOtherClass data; // Rest of the class. public Object clone () throws CloneNotSupportedException { // The cast in the line below throws an exception! // ChildClass newObject = (ChildClass)super.clone(); // You _never_ get here because the line above throws // an exception. if (this.data != null) newObject.data = (SomeOtherClass)this.data.clone(); return newObject; } }
The first line in clone throws an exception because the clone method in BaseClass returns a BaseClass object not a ChildClass object.
Summary: Don't implement clone by using a copy constructor.
Avoid using constructors to copy subobjects when possible. 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 void setDimension (Dimension dim) { this.dim = 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 sub-class of // Dimension, any data in the sub-class (e.g. a third // dimension value like z) will be lost. // if (this.dim != null) newObject.dim = new Dimension (dim); return newObject; } }
If a child class of Dimension is passed to setDimension, the object returned by clone will be different from the original object. The preferred way to write this clone method would be:
import java.awt.Dimension; public class Example implements Cloneable { private Dimension dim; public void setDimension (Dimension dim) { this.dim = dim; } public Object clone () throws CloneNotSupportedException { Example newObject = (Example)super.clone(); // Call 'this.dim.clone()' instead of // 'new Dimension(dim)' // if (this.dim != null) newObject.dim = (Dimension)this.dim.clone(); return newObject; } }
Now, if a child class of Dimension is passed to setDimension, it is copied properly when clone is called.
Unfortunately, while the preferred code above compiles under the Java 2 platform (formerly known as JDK 1.2), it won't compile under JDK 1.1.7. Dimension doesn't implement Cloneable in JDK1.1 and the clone method for Dimension is protected so Example can't call it anyway. This means that under JDK 1.1 you must write Example's clone method using a copy constructor for the Dimension member variable even though you don't want to. If a child of Dimension is passed to setDimension, you'll have a problem if you try to clone an Example object.
Testing explicitly for Dimension in the clone method is one workaround:
import java.awt.Dimension; public class Example implements Cloneable { private Dimension dim; public void setDimension (Dimension dim) { this.dim = dim; } public Object clone () throws CloneNotSupportedException { Example newObject = (Example)super.clone(); if (this.dim != null) { // Test explicitly for Dimension here. Don't test // using the instanceof operator -- it doesn't do // what you want it to. // if (this.dim.getClass() != Dimension.class) throw new CloneNotSupportedException("Wrong sub-class for 'dim'"); newObject.dim = new Dimension (dim); } return newObject; } }
This is better than returning a clone object with the wrong data for dim, but it still isn't a good solution.
Summary: Make copies of member variables using their clone methods if possible.
Pay attention to synchronization on the clone method. clone is a method just like any other. In a multithreaded environment you want to synchronize clone so that the underlying object stays internally consistent while being copied. You must then also synchronize the mutator methods. Note that this is different from a constructor, which almost never needs synchronization.
Sometimes you should treat clone like a constructor. Even though the clone method isn't a constructor, sometimes you should treat it like one. 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.
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 -- see the problems with Dimension in (2) above. If you're producing a third-party library, don't force your customers to work around a lack of cloning.
If you're not producing a third-party library, waiting to implement clone until it's needed for each class is reasonable. This is especially true because once you've overridden clone, you must pay careful attention to overriding clone in all the child classes.
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. If it doesn't, you must override clone.
Next page >Page 1 How to avoid traps and correctly override methods from java.lang.Object Page 2 Implementing equals and hashCode Page 3 Implementing toString
500)this.width=500'> Printer-friendly version | 500)this.width=500'> Mail this to a friend
Resources
Both the javadoc and the source code for the methods in java.lang.Object can be found at Sun's Java Web site http://java.sun.com
The Eiffel programming language takes the notion of design-by-contract very seriously. This paper provides a good discussion of this notion http://www.eiffel.com/doc/manuals/technology/contract/index.html
Shallow copying versus deep copying
Shallow copying means that member variables are copied to the clone object while subobjects only have their references copied -- so no new subobjects are created. An example is:
import java.awt.Dimension;
public class Example implements Cloneable
{
private Dimension dim = new Dimension();
public synchronized Object clone () throws
CloneNotSupportedException
{
return super.clone();
}
public synchronized void setWidth (int width)
{
dim.width = width;
}
public synchronized int getWidth ()
{
return dim.width;
}
}
Creating an object of type Example and then cloning it will result in only one object of type Dimension, to be shared by both Example objects. The example below shows this:
public class Main
{
public static void main (String[] args)
{
try
{
Example e1 = new Example();
System.out.println ("e1 width = " + e1.getWidth());
Example e2 = (Example)e1.clone();
System.out.println ("e2 width = " + e2.getWidth());
System.out.println ("Setting e1 width to 10");
e1.setWidth (10);
System.out.println ("e1 width = " + e1.getWidth());
System.out.println ("e2 width = " + e2.getWidth());
}
catch (CloneNotSupportedException exception)
{
System.out.println ("Ooops");
}
}
}
When the example above is run, the output is:
e1 width = 0
e2 width = 0
Setting e1 width to 10
e1 width = 10
e2 width = 10
Notice that setting the width in e1 also sets the width in e2 because they're sharing the underlying Dimension object.
Deep copying means that each object gets its own copy of the subobjects. If you want clone to implement a deep copy, you must perform this copying yourself. To make the clone method for Example return a deep copy, you would write clone like this:
import java.awt.Dimension;
public class Example implements Cloneable
{
private Dimension dim = new Dimension();
public synchronized Object clone () throws
CloneNotSupportedException
{
Example copy = (Example)super.clone();
copy.dim = new Dimension (dim);
return copy;
}
public synchronized void setWidth (int width)
{
dim.width = width;
}
public synchronized int getWidth ()
{
return dim.width;
}
}
When you run the example with the new version of clone, the output is:
e1 width = 0
e2 width = 0
Setting e1 width to 10
e1 width = 10
e2 width = 0
|
|
|