Java 为什么';CopyOnWriteArraySet实现可克隆接口,而CopyOnWriteArraySet实现可克隆接口?

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

在这篇文章中,Doug Lea写道(参考JDK 5.0的预发布版本):

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()
      进行实际克隆
    • 可能通过深度复制内部结构来修改克隆对象
    • 返回克隆的对象
历史上,所有集合实现都支持克隆。在JDK 5.0发布之前,
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呢?它没有任何最终字段。好吧,当时确实是这样,所以它正是受到美国的问题的影响