当前位置: 首页 > 知识库问答 >
问题:

为什么CopyOnWriteArraySet不实现可克隆接口,而CopyOnWriteArrayList却实现了呢?

闻人英韶
2023-03-14

在这个bug报告中,Doug Lea写道(指的是JDK 5.0的预发布版本):

附言。我知道clone()不推荐使用,并且copyonwritearrayset内部基于copyonwritearraylist

共有1个答案

姬乐
2023-03-14

在一个机密数据库中有一些关于此bug的重要信息(JDK-5055732)。我已经在一个关于那个bug的公开评论中发布了这个信息,我将复制到这里来回答这个问题。

问题

正如Josh Bloch的《Effective Java》中所解释的那样,可克隆机制设计得并不是很好。特别是,对于一个具有final引用字段的非final类来说,要满足以下要求是不可能的:

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

实际上,程序员假设如果他们扩展了一个类并从子类中调用super.clone,返回的对象将是该子类的实例。超类提供此功能的唯一方法是返回通过调用super.clone获得的对象。如果克隆方法返回由普通构造函数创建的对象,则它将没有正确的类。因此,如果在非final类中重写克隆方法,则应始终返回通过调用super.clone()获得的对象。

一般来说,这意味着任何具有空白final字段的类都会遇到问题,因为它需要在Clone中设置字段。现在可以在JDK类中使用setAccessible漏洞(请参见JMM列表)来实现这一点,但这既难看又慢。删除“可克隆的实现”似乎是个更好的主意。

ConcurrentHashMap类具有完全相同的问题和相同的解决方案。“

上面的文本解释了一切,但它可能有点混乱,因为它解释了与不再可见的代码状态相关的事情,而且它还假设了相当多的上下文知识。这里有一个解释,我想可能更容易理解。

有关克隆的问题在Joshua Bloch的《Effective Java》第11项中有充分的解释。许多问题也在堆栈溢出的其他地方进行了讨论。简单地说,要允许成功克隆,类必须

  • 实现cloneable接口
  • 实现public clone()方法
  • clone()方法中,必须
    • 调用super.clone()进行实际克隆
    • 修改克隆对象,可能是通过深度复制内部结构
    • 返回克隆的对象

    事实证明,CopyOnWriteArraySetConcurrentHashMap都不能履行支持克隆的所有义务。对bug的“修复”是让它们退出cloneable协定。

    不能克隆CopyOnWriteArraySet的原因是它有一个final字段al,该字段指向存储实际元素的CopyOnWriteArrayList。克隆不能与原始的共享此状态,因此需要clone()方法复制(或克隆)后备列表并将其存储到字段中。但是final字段只能存储在构造函数中,而clone()不是构造函数。实现者考虑并拒绝了使用反射来编写final字段等英勇的努力。

    像这样的一行程序构造函数呢?

        public clone() { return new CopyOnWriteArraySet(al); }
    

    最近版本的ConcurrentHashMap不再有final字段。copy构造函数只是在map参数上调用putall,这会懒洋洋地初始化所有字段。clone()方法不能简单地通过克隆、取消所有字段的值,然后调用putall()来实现吗?

    这似乎是可行的,但我怀疑它与内存模型相冲突。并不是所有的字段都是不稳定的。即使所有字段在重新初始化以指向副本之前都被取消,其他线程也可能会看到仍然指向原始映射的陈旧值。可能有方法可以避免这个问题,但我怀疑实现者认为提供克隆性不值得付出额外的努力。

 类似资料:
  • 您会注意到,我已经在目录本身的克隆方法中克隆了一个product类对象,但是您可能会注意到,主类已经测试了它,并且它工作了,所以product类的克隆是成功的。 你能看出问题所在吗?我知道也会有很多小错误的编码礼仪和可能更容易的方法做事情,但我只是一个初学者。我必须实现deep copy,而且它必须有意义,所以克隆product类是不够的,我想创建一个方法来克隆整个目录,所以如果要用一台新的机器替

  • 问题内容: @Entity public class Husband implements Serializable { 广义上是什么? 类为什么实现接口? 为什么丈夫成员一个人只有@OnetoOne(mappedBy =“ Wife”),而妻子成员却没有@OnetoOne(mappedBy =“ husband”) 问题答案: 广义上讲,序列化是Java为开发人员提供的将任何对象的状态持久保存到

  • ViewGroup扩展了View并实现了ViewParent,但是View具有与ViewParent相同的所有功能,比如getParent()等。 如果View实现ViewParent会更好吗?

  • 问题内容: 根据Java文档中的Serializability: 通过实现java.io.Serializable接口的类,可以启用类的可序列化性。未实现此接口的类将不会对其状态进行序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义 为什么对象尚未实现?我们不希望可序列化的成员可以设为。为什么要阻止默认的Serializability?

  • 我遇到了一些实现的类代码,文档中写道: 类实现Cloneable接口,以向object.clone()方法表明,该方法对该类的实例进行字段对字段的复制是合法的。在未实现可克隆接口的实例上调用对象的克隆方法会导致引发异常CloneNotSupportedException。按照约定,实现此接口的类应该使用公共方法重写Object.clone(受保护)。有关重写此方法的详细信息,请参见object.c

  • 假设我有这门课: 以及子类: 我知道这是不可能的,但我想你明白我想要什么。如果Foobar实现了Cloneable,并且没有扩展AbstractFoo,那么子类就可以工作。我想我想要但不允许的是: 如果Foobar实现了Cloneable,并且没有扩展AbstractFoo,那么子类就可以工作。 除了扩展的抽象类,我怎么能做到“相同”?