Lrn*_*Boy 1 java collections clone cloneable
在这个错误报告中,Doug Lea写道(指的是JDK 5.0的预发布版本):
虽然
CopyOnWriteArraySet声明Cloneable,它没有界定公共clone方法.
但它最终会CopyOnWriteArraySet完全没有实现Cloneable接口!(在Java SE 6,7和8中都是如此)
如何CopyOnWriteArraySet从不同的CopyOnWriteArrayList关于克隆?有没有人想要克隆它?
PS我明白clone()不推荐这CopyOnWriteArraySet是基于CopyOnWriteArrayList内部的.
在机密数据库中有关于此错误(JDK-5055732)的一些重要信息.我已经在这个bug的公开评论中发布了这些信息,我会在这里复制它来回答这个问题.
问题
正如Josh Bloch的Effective Java中所解释的那样,Cloneable机制设计得并不是很好.特别是,对于每个对象而言,具有最终引用字段的非final类不可能是唯一的,以满足以下要求:
Run Code Online (Sandbox Code Playgroud)x.clone().getClass() == x.getClass()(当类被子类化时)
CopyOnWriteArraySet,ConcurrentHashMap当前被指定用于实现Cloneable.CopyOnWriteArraySet错误地没有实现公共clone()方法,而ConcurrentHashMap使用构造函数实现了clone()方法,因此无法满足上述要求.
Doug Lea写道:
"Martin和Josh说服我,我们不能只添加单行公共Object clone(){return new CopyOnWriteArraySet(al);}因为正如Josh在Effective Java书中所指出的那样,clone方法不应该调用构造函数:
在实践中,程序员假设如果他们扩展一个类并从子类中调用super.clone,则返回的对象将是子类的实例.超类可以提供此功能的唯一方法是返回通过调用super.clone获得的对象.如果clone方法返回由普通构造函数创建的对象,则它将没有正确的类.因此,如果在非final类中重写clone方法,则应始终返回通过调用super.clone()获得的对象.
通常,这意味着任何具有空白最终字段的类都会遇到问题,因为它需要在克隆中设置字段.现在可以在JDK类中使用setAccessible漏洞(参见JMM列表),但这很丑陋.删除"implements Cloneable"似乎是一个更好的主意.
ConcurrentHashMap类有完全相同的问题和相同的解决方案."
解
从CopyOnWriteArraySet,ConcurrentHashMap的规范中删除"implements Cloneable".删除ConcurrentHashMap.clone()
上面的文字解释了所有内容,但它可能有点令人困惑,因为它解释了与不再可见的代码状态相关的事情,并且它还假设了相当多的上下文知识.这是一个我认为可能更容易理解的解释.
有关克隆的问题在Joshua Bloch的Effective Java,第11项中得到了充分解释.许多问题也在Stack Overflow的其他地方也有所涉及.简而言之,为了成功克隆,一堂课必须
Cloneable界面public clone()方法clone()方法中,它必须
super.clone()去做实际的克隆从历史上看,所有集合实现都支持克隆.在JDK 5.0之前的版本,CopyOnWriteArraySet并且ConcurrentHashMap都实现的Cloneable接口.但是CopyOnWriteArraySet没有实现一个public clone()方法,并且虽然ConcurrentHashMap实现了一个public clone()方法,但是通过返回一个新构造的实例,它做错了ConcurrentHashMap.这两个都是错误,是这个错误报告的主题.
事实证明,既不能CopyOnWriteArraySet也ConcurrentHashMap不能履行支持克隆的所有义务.然后,错误的"修复"是让他们退出Cloneable合同.
CopyOnWriteArraySet无法克隆的原因是它有一个al指向CopyOnWriteArrayList存储实际元素的final字段.克隆不得与原始状态共享此状态,因此clone()需要复制(或克隆)支持列表并将其存储到字段中的方法.但是最终字段只能存储在构造函数中,而clone()不是构造函数.实现者考虑并拒绝了英雄的努力,例如使用反射来编写最终字段.
像这样的单线构造器怎么样?
public clone() { return new CopyOnWriteArraySet(al); }
Run Code Online (Sandbox Code Playgroud)
这里的问题是它打破了克隆合同.如果子类CopyOnWriteArraySet支持克隆,则调用clone()该子类应该返回该子类的实例.clone()子类的方法可以正确调用super.clone()以创建克隆.如果它是如上所述实现的,那将返回一个实例CopyOnWriteArraySet而不是子类的实例.因此,这将阻止子类能够克隆自己.
怎么样ConcurrentHashMap?它没有任何最终字段.嗯,它当时做了,所以它完全遇到了从方法中更新最终字段的问题clone().
最新版本ConcurrentHashMap不再有最终字段.复制构造函数只是调用putAllmap参数,它会懒惰地初始化所有字段.难道这个clone()方法不能简单地通过克隆,将所有字段归零,然后调用来实现putAll()吗?
这似乎可能有用,但我怀疑它与内存模型相冲突.并非所有字段都是易变的.即使在重新初始化以指向副本之前所有字段都被清空,其他线程也可能会看到仍然指向原始地图的陈旧值.可能有办法避免这个问题,但我怀疑实现者认为提供可克隆性不值得付出额外的努力.
| 归档时间: |
|
| 查看次数: |
155 次 |
| 最近记录: |