建议:谨慎地覆盖clone。

Cloneable接口的目的是作为对象的一个mixin接口(mixin interface),表明这样的对象允许克隆(clone)。遗憾的是,他并没有成功地达到这个目的。其主要的缺陷在于它缺少一个clone方法,Object的clone方法是受保护的。如果不借助于反射(reflection),就不能仅仅因为一个对象实现了Cloneable,就可以调用clone方法。即使是反射调用也可能会失败,因为不能保证该对象一定具有可访问的clone方法。

既然Cloneable并没有包含任何方法,那么他到底有什么作用呢?他决定了Object中受保护的clone方法实现的行为;如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。这是接口的一种极端非典型的用法,也不值得效仿。通常情况下,实现接口是为了表明类可以为它的客户做些什么。然而对于Cloneable接口,它改变了超类中受保护的方法的行为。

如果实现Cloneable接口是要对某个类起到作用,类和它的所有超类都必须遵守一个相当复杂的、不可实施的,并且基本上没有文档说明的协议。由此得到一种语言之外的(extralinguistic)机制:无需调用构造器就可以创建对象。

Clone方法的通用约束是非常弱的,下面是来自java.lang.Object规范中的约定内容【JavaSE6】:

创建和返回该对象的一个拷贝。这个“拷贝”的精确含义取决于该对象的类。一般的含义是,对于任何对象x,表达式

x.clone() != x

将会是ture,并且,表达式

x.clone().getClass() == x.getClass()

将会是true,但这些都不是绝对的要求。虽然通常情况下,表达式

x.clone().equals(x)

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

1.5发型版本中引入了协变返回类型(convariant return type)作为泛型。换句话说,目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类了。这样有助于覆盖方法提供更多关于被返回对象的信息,并且在客户端中不必进行转换。这里体现了一条通则:永远不要让客户去做任何类库能够替客户完成的事情。

实际上,clone方法就是另一个构造器;你必须确保他不会伤害到原始的对象,并确保正确的创建被克隆对象中的约定条件(invariant)。

还要注意,clone架构与引用可变对象的final域的正常用法是不相兼容的,除非在原始对象和克隆对象之间可以安全的共享此可变对象。为了使类称为可克隆的,可能有必要从某些域中去掉final修饰符。

克隆复杂对象的一种办法是,先调用super.clone,然后把结果对象中的所有域都设置成他们的空白状态(virgin state),然后调用高层(higher-level)的方法来重新产生对象的状态。

还有一点值得注意。如果你决定用线程安全的类实现Cloneable接口,要记得它的clone方法必须得到很好地同步,就像任何其他方法一样。Object的clone方法没有同步,因此即使很满意,可能也必须编写同步的clone方法来调用super.clone()。

简而言之,所有实现了Cloneable接口的类都应该用一个公有的方法覆盖clone。此公有方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,这意味着要拷贝任何包含内部“深层结构”的可变对象,并用指向新对象的引用代替原来指向这些对象的引用。虽然,这些内部拷贝操作往往可以通过递归的调用clone来完成,但这通常并不是最佳方法。如果该类只包含基本类型的域,或者指向不可变对象的引用,那么多半的情况是没有域需要修正。这套规则也有例外,譬如,代表序列号或其他唯一ID值得域,或者代表对象的创建时间的域,不管这些域是基本类型还是不可变的,他们也都需要被修正。

真的有必要这么复杂吗?很少有这种必要。如果你扩展一个实现了Cloneable接口的类,那么你除了实现一个行为良好的clone方法外,没有别的选择。否则,最好提供某些其他的途径太代替对象拷贝,或者干脆不提供这样的功能。例如,对于不可变类,支持对象拷贝并没有太大的意义,因为被拷贝的对象与原始对象并没有实质的不用。

一个实现对象拷贝的好办法是提供一个拷贝构造器(copy constructor)或拷贝工厂(copy factory)。拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类,例如:

public Yum(Yum yum);

拷贝工厂是类似于拷贝构造器的静态工厂:

public static Yum newInstance(Yum yum);

拷贝构造器的做法,及其静态工厂方法的变形,都比Cloneable/clone方法具有更多的优势:他们不依赖于某一种很有风险的、语言之外的对象创建机制;他们不要求遵守尚未制定好文档的规范;他们不会与final域的正常使用发生冲突;他们不会抛出不必要的受检异常(checked exception),他们不需要进行类型转换。虽然你不可能把拷贝构造器或静态工厂放到接口中,但是由于Cloneable接口缺少一个公有的clone方法,所以它也没有提供一个接口该有的功能。因此,使用拷贝构造器或者拷贝工厂来代替clone方法时,并没有放弃接口的功能特性。

更进一步,拷贝构造器或者拷贝工厂可以带一个参数,参数类型是通过该类实现的接口。例如,按照惯例,所有通用集合实现都提供了一个拷贝构造器,它的参数类型为Collection或者Map。基于接口的拷贝构造器和拷贝工厂(更准确的叫法应该是“装换构造器(conversion constructor)”)和转换工厂(conversion factory)),允许客户选择拷贝的实现类型,而不是强迫客户接受原始的实现类型。例如,假设你有一个HashSet,并且希望把他拷贝成一个TreeSet,clone方法无法提供这样的功能,但是用转换构造器很容易实现:new TreeSet(s)。

既然Cloneable具有上述这么多问题,可以肯定的说,其他的接口都不应该扩展(extend)这个接口,为了继承而设计的类也不应该实现(implement)这个接口。由于具有这么多缺点,有些专家级的程序员干脆从来不去覆盖clone方法,也从来不去调用他,除非拷贝数组。你必须清楚一点,对于一个专门为了继承而设计的类,如果你未能提供行为良好的受保护的(protected)clone方法,它的子类就不可能实现Cloneable接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值