读书人

《Effective Java》读书笔记09-审慎地

发布时间: 2013-04-09 16:45:09 作者: rapoo

《Effective Java》读书笔记09--谨慎地覆盖clone方法

一、Cloneable接口与Object.clone方法

Cloneable接口的目的是作为对象的一个mixin接口(混合型接口),表明这样的对象允许克隆(clone)。遗憾的是Cloneable接口里并没有clone方法,其实它什么方法都没有,跟Serializable接口一样,都是占着茅坑不拉屎。它只是声明该对象可以被克隆,具体行为由类设计者决定。如果类设计者忘记提供一个良好的clone方法或根本不提供clone方法,那么类客户使用时必定会出错,这样Cloneable接口并没达到它的目的。

Cloneable接口决定了Object中受保护的clone方法的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。这是接口的极端非典型的用法,不值得效仿,因为它违背了接口的使用规范,改变了超类中受保护的方法的行为。

Cloneable接口约束实现类及其所有超类都必须遵守一种语言之外的机制:无需调用构造器就可以创建对象。通常这种机制的通用约定是非常弱的。它要求实现类和父类必须提供一个Clone方法,在Clone方法中调用super.clone方法,已达到最终调用Object.clone方法来完成约定。如果有一层没有按照约定实现,那么该类的Clone功能将是潜在的灾难。

二、Object中Clone方法的通用约定

Clone方法用于创建和返回对象的一个拷贝,一般含义如下:

1、对于任何对象x,表达式 x.clone()!=x 将会是true,并且表达式 x.clone().getClass() == x.getClass()将会是true,但这不是绝对要求。

2、通常情况下,表达式 x.clone.equals(x)将会是true,同1一样这不是绝对要求。

拷贝对象往往会导致创建它的类的一个新实例,但它同时也要求拷贝内部的数据接口,这个过程中没有调用构造器。

该通用约定存在的问题:

1、不调用构造器的规定太强硬

行为良好的clone方法可以调用构造器来创建对象,构造之后再复制内部数据。如果这个类是final的,clone甚至可能会返回一个由构造器创建的对象。既然类是final的,不可变的,我当然可以调用构造器创建一个实例,甚至缓存起来(单例模式),等调用clone时直接返回该对象,这样效率更高。

2、x.clone().getClass()通常应该等同于x.getClass()的规定太软弱

在实践中,我们一般会假设:如果扩展一个类,并在子类中调用了super.clone,返回的对象就将是该子类的实例(我们要克隆的是子类而不是父类)。

超类提供此功能的唯一途径是:返回一个通过调用super.clone而得到的对象。如果clone方法返回一个由构造器创建的对象,它就会得到错误的类(当前父类而不是想要的子类)。

因此,如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。如果类的所有超类都遵守这条规则,那调用super.clone方法最终会调用Object.clone方法,从而创建正确类的实例,此机制类似于自动的构造器调用链,只不过它不是强制要求的。

三、实现一个行为良好的clone方法

从super.clone()中得到的对象有时接近于最终要返回的对象,有时会相差很远,这取决于该类的本质。

1、每个域包含的只有基本类型或指向不可变对象的引用,这种情况返回的对象可能满足我们的需要,比如《读书笔记08》中的PhoneNumber类。在此,我们只需声明实现Cloneable接口,然后对Object中受保护的clone方法提供公有的访问途径:

//Copy constructorpublic Yum(Yum yum);//Copy factorypublic static Yum newInstance(Yum yum);
此方式优点:

1、它们不依赖于某一种很有风险的,语言之外的对象创建机制。

2、它们不要求遵守尚未制定好的文档规范。

3、它们不会与final的正常使用发生冲突。

4、它们不会抛出不必要的受检异常。

5、它们不需要进行类型转换。

6、它们可以带一个参数,参数类型是通过该类实现的接口,比如集合框架。

五、最佳编程实践

如果必须提供clone方法:

1、clone方法不应该在构造的过程中,调用新对象中任何非final的方法,会造成克隆对象与原始对象的状态不一致。

2、公有的clone方法应该省略CloneNotSupportException异常,因为这样使用起来更轻松。如果专门为了继承而设计的类覆盖了clone方法,覆盖版本的clone方法就应该模拟Object.clone的行为:它应该被声明为protected,抛出CloneNotSupportException异常,并且该类不应该实现Cloneable接口,以便子类可以自己决定是否实现它。

3、用线程安全的类实现Cloneable接口,要记得它的clone方法必须得到很好地同步。

4、任何实现了Cloneable接口的类都应该用一个公有的方法覆盖clone。此方法首先调用super.clone,然后修正任何需要修正的域。

5、使用拷贝构造器或拷贝工厂来代替clone方法

读书人网 >编程

热点推荐