Java 为什么';CopyOnWriteArraySet实现可克隆接口,而CopyOnWriteArraySet实现可克隆接口?
在这篇文章中,Doug Lea写道(参考JDK 5.0的预发布版本): 当Java 为什么';CopyOnWriteArraySet实现可克隆接口,而CopyOnWriteArraySet实现可克隆接口?,java,collections,clone,cloneable,Java,Collections,Clone,Cloneable,在这篇文章中,Doug Lea写道(参考JDK 5.0的预发布版本): 当CopyOnWriteArraySet被声明为Cloneable时,它无法定义公共克隆方法 但最终,CopyOnWriteArraySet根本没有实现Cloneable接口!(在JavaSE6、7和8中也是如此。) 在克隆方面,CopyOnWriteArraySet与CopyOnWriteArraySet有何不同?没有人想克隆它是有充分理由的吗 另外,我知道不建议使用clone(),而且CopyOnWriteArraySe
CopyOnWriteArraySet
被声明为Cloneable
时,它无法定义公共克隆方法
但最终,CopyOnWriteArraySet
根本没有实现Cloneable
接口!(在JavaSE6、7和8中也是如此。)
在克隆方面,CopyOnWriteArraySet
与CopyOnWriteArraySet
有何不同?没有人想克隆它是有充分理由的吗
另外,我知道不建议使用
clone()
,而且CopyOnWriteArraySet
是基于CopyOnWriteArraySet
内部的。在机密数据库中有一些关于此bug()的重要信息。我已经在那个bug的公开评论中发布了这个信息,我将把它复制到这里来回答这个问题
问题
正如Josh Bloch在《高效Java》中所解释的那样,可克隆机制的设计不是非常好。特别是,对于具有最终引用字段的非最终类,每个对象都需要唯一的最终引用字段才能满足以下要求:
x.clone().getClass() == x.getClass()
(当类是子类时)
CopyOnWriteArraySet、ConcurrentHashMap当前被指定为实现可克隆。CopyOnWriteArraySet错误地没有实现公共clone()方法,而ConcurrentHashMap使用构造函数实现了clone()方法,因此没有满足上述要求
Doug Lea写道:
“Martin和Josh说服我,我们不能只添加一行公共对象clone(){return new CopyOnWriteArraySet(al);},因为正如Josh在《有效的Java手册》中指出的那样,clone方法不应该调用构造函数:
实际上,程序员假定,如果他们扩展一个类并从子类中调用super.clone,则返回的对象将是该子类的实例。超类提供此功能的唯一方法是返回通过调用super.clone获得的对象。如果clone方法返回由正常构造创建的对象因此,如果在非final类中重写clone方法,则应始终返回通过调用super.clone()获得的对象
一般来说,这意味着任何具有空白最终字段的类都会遇到问题,因为它需要在克隆中设置字段。现在,使用setAccessible漏洞(请参见JMM列表)在JDK类中可以实现这一点,但这既丑陋又缓慢。删除“implements Cloneable”似乎是一个更好的主意
ConcurrentHashMap类具有完全相同的问题和相同的解决方案。”
解决方案
从CopyOnWriteArraySet、ConcurrentHashMap的规范中删除“implements Cloneable”。删除ConcurrentHashMap.clone()
上面的文本解释了一切,但可能有点混乱,因为它解释了与不再可见的代码状态相关的事情,并且它还假设了相当数量的上下文知识。这是一个我认为可能更容易理解的解释
约书亚·布洛赫(Joshua Bloch)的《有效的Java》第11项充分解释了克隆的问题。许多问题也包括在内。简单地说,要允许成功克隆,类必须
- 实现
接口Cloneable
- 实现一个
方法公共克隆()
- 在
方法中,它必须clone()
- 调用
进行实际克隆super.clone()
- 可能通过深度复制内部结构来修改克隆对象
- 返回克隆的对象
- 调用
CopyOnWriteArraySet
和ConcurrentHashMap
都实现了Cloneable
接口。但是copyonWriteraraySet
没有实现public clone()
方法,虽然ConcurrentHashMap
实现了public clone()
方法,但它这样做是错误的,方法是返回新构造的ConcurrentHashMap
。这两个都是bug,都是本bug报告的主题
事实证明,无论是CopyOnWriteArraySet
还是ConcurrentHashMap
都不能履行支持克隆的所有义务。那么,这个bug的“修复”就是让他们退出Cloneable
合同
无法克隆CopyOnWriteArraySet
的原因是它有最后一个字段al
,该字段指向存储实际元素的CopyOnWriteArraySet
。克隆不得与原始克隆共享此状态,因此需要使用clone()
方法复制(或克隆)备份列表并将其存储到字段中。但是final字段只能存储在构造函数中,clone()
不是构造函数。实现者考虑并拒绝了英勇的努力,例如使用反射来编写最终字段
像这样的一行程序构造函数呢
public clone() { return new CopyOnWriteArraySet(al); }
这里的问题是它破坏了克隆合同。如果CopyOnWriteArraySet
的子类支持克隆,则对该子类调用clone()
应返回该子类的实例。子类的clone()
方法将正确调用super.clone()
来创建克隆。如果按照上面的方式实现,那么将返回CopyOnWriteArraySet
的实例,而不是子类的实例。因此,这将阻止子类克隆自己
那ConcurrentHashMap呢?它没有任何最终字段。好吧,当时确实是这样,所以它正是受到美国的问题的影响