本站首页    管理页面    写新日志    退出


«July 2025»
12345
6789101112
13141516171819
20212223242526
2728293031


公告

关注电子政务、大型企业应用开发、Web、Workflow、MOM、MDA、RCP、GEF

email:gmluyang@gmail.com


链接

好友
bluedavy-林昊
刑红瑞
SixSun-翻译
Birt中文指南
SixSun-OpenDoc
Eclipse RCP and Web in action(编写中)

我的分类(专题)

日志更新

最新评论

留言板


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


阅读全文(3099) | 回复(0) | 编辑 | 精华
 



发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.047 second(s), page refreshed 144775837 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号