本文介绍了为什么CopyOnWriteArraySet不实现Cloneable接口,而CopyOnWriteArrayList呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Doug Lea在中写道(指版本的JDK 5.0):

In this bug report, Doug Lea writes (referring to a pre-release version of JDK 5.0):

但最终最终导致 CopyOnWriteArraySet 根本没有实现 Cloneable 接口! (在Java SE 6、7和8中是这样。)

But it eventually ends up that CopyOnWriteArraySet doesn't implement the Cloneable interface at all! (This is true in Java SE 6, 7, and 8.)

CopyOnWriteArraySet 与<$有何不同? c $ c> CopyOnWriteArrayList 关于克隆?有一个合理的原因,没人想克隆它?

How is CopyOnWriteArraySet different from CopyOnWriteArrayList with respect to cloning? There is a sound reason nobody ever wants to clone it?

P.S。我了解不推荐使用 clone(),并且 CopyOnWriteArraySet 是基于 CopyOnWriteArrayList 内部。

P.S. I understand that clone() is not recommended and that CopyOnWriteArraySet is based on CopyOnWriteArrayList internally.

推荐答案

有关此错误的一些重要信息()。我已将此信息发布到有关该错误的公开评论中,并将其复制到此处以回答此问题。

There was some important information about this bug (JDK-5055732) in a confidential database. I've posted this information in a public comment on that bug, and I'll copy it here to answer this question.

正如Josh Bloch的Effective Java中所述,Cloneable机制的设计并非十分出色。特别是,对于每个对象都需要唯一引用的最终引用字段的非最终类而言,不可能满足以下要求:

As explained in Josh Bloch's Effective Java, the Cloneable mechanism is not terribly well designed. In particular, it is impossible for a non-final class with a final reference field that needs to be unique for each object to satisfy the requirement that

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

(当类被子类化时)

CopyOnWriteArraySet,ConcurrentHashMap当前已指定为实现Cloneable。 CopyOnWriteArraySet错误地没有实现公共的clone()方法,而ConcurrentHashMap使用构造函数实现了clone()方法,从而不能满足上述要求。

CopyOnWriteArraySet, ConcurrentHashMap currently are specified to implement Cloneable. CopyOnWriteArraySet erroneously did not implement a public clone() method, while ConcurrentHashMap implemented a clone() method using a constructor, thereby not fulfilling the above requirement.

Doug Lea写道:

Doug Lea writes:

Martin和Josh说服我,我们不能只添加单行公共Object clone(){return new CopyOnWriteArraySet(al); },因为正如Josh在Effective Java一书中所指出的那样,克隆方法不应调用构造函数:

"Martin and Josh convinced me that we can't just add the one-line public Object clone() { return new CopyOnWriteArraySet(al); } because, as noted by Josh in Effective Java book, clone methods should not invoke constructors:

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

In practice, programmers assume that if they extend a class and call super.clone from within the subclass, the returned object will be an instance of the subclass. The only way that a superclass can provide this functionality is to return an object obtained by calling super.clone. If a clone method returns an object created by a normal constructor, it will not have the correct class. Therefore, if you override the clone method in a non-final class, you should always return an object obtained by invoking super.clone().

通常,这意味着任何具有空白的final字段会遇到问题,因为它需要在clone中设置该字段。现在可以在JDK类中使用setAccessible漏洞实现此功能(请参阅JMM列表),但它又难看又缓慢。似乎只是删除 implements Cloneable。

In general this means that any class with a blank final field will encounter problems because it needs to set the field inside clone. This is now possible inside JDK classes using the setAccessible loophole (see JMM list) but is ugly and slow. It seems like a better idea just to remove "implements Cloneable".

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

The ConcurrentHashMap class has exactly the same problem, and the same solution."

解决方案

删除可克隆实现;从CopyOnWriteArraySet,ConcurrentHashMap的规范中获取。删除ConcurrentHashMap.clone()

Remove "implements Cloneable" from the specification for CopyOnWriteArraySet, ConcurrentHashMap. Delete ConcurrentHashMap.clone()

上面的文本解释了所有内容,但是由于它解释了与代码状态有关的内容,因此可能有些混乱不再可见,并且还假设有大量的上下文知识。这是一个我认为可能更容易理解的解释。

The text above explains everything, but it might be a bit confusing since it explains things relative to the state of the code that's no longer visible, and it also assumes a fair amount of contextual knowledge. Here's an explanation that I think might be a bit easier to understand.

克隆的问题在Joshua Bloch的 Effective Java 项目11中已得到充分解释。这些问题也进行了介绍。简短地说,要成功克隆,一个类必须

The issues with cloning are fully explained in Joshua Bloch's Effective Java, Item 11. Much of the issues are also covered elsewhere on Stack Overflow. Briefly, to allow successful cloning, a class must


  • 实现 Cloneable 接口

  • clone()中实现公共clone()方法

  • code>方法,必须调用

    • 调用 super.clone()进行实际克隆

    • 可能通过深复制内部结构来修改克隆的对象

    • 返回克隆的对象

    • implement the Cloneable interface
    • implement a public clone() method
    • in the clone() method, it must
      • call super.clone() to do the actual cloning
      • modify the cloned object, possibly by deep-copying internal structures
      • return the cloned object

      从历史上看,所有集合实现都支持克隆。在JDK 5.0发行之前, CopyOnWriteArraySet ConcurrentHashMap 都实现了 Cloneable 界面。但是 CopyOnWriteArraySet 并没有实现 public clone()方法,而 ConcurrentHashMap 确实实现了 public clone()方法,但这样做不正确,因为它返回了 ConcurrentHashMap 。这些都是错误,并且都是此错误报告的主题。

      Historically, all collections implementations have supported cloning. Prior to the release of JDK 5.0, CopyOnWriteArraySet and ConcurrentHashMap both implemented the Cloneable interface. But CopyOnWriteArraySet didn't implement a public clone() method, and while ConcurrentHashMap did implement a public clone() method, it did so incorrectly, by returning a freshly constructed instance of ConcurrentHashMap. Both of these are bugs and are the subject of this bug report.

      事实证明, CopyOnWriteArraySet 和 ConcurrentHashMap 可以履行支持克隆的所有义务。该修复指的是修复。那么,导致该错误的原因是让它们从 Cloneable 合同中撤回。

      It turns out that neither CopyOnWriteArraySet nor ConcurrentHashMap can fulfill all the obligations of supporting cloning. The "fix" for the bug, then, was to have them withdraw from the Cloneable contract.

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

      The reason CopyOnWriteArraySet can't be cloned is that it has a final field al that points to the CopyOnWriteArrayList that stores the actual elements. The clone mustn't share this state with the original, so the clone() method would be required to copy (or clone) the backing list and store it into the field. But final fields can only be stored within constructors, and clone() isn't a constructor. The implementors considered and rejected heroic efforts such as using reflection to write final fields.

      像这样的单线构造函数呢?

      What about a one-liner constructor such as this?

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

      这里的问题是它破坏了克隆合同。如果 CopyOnWriteArraySet 的子类支持克隆,则在该子类上调用 clone()应该返回该子类的实例。 。子类的 clone()方法将正确调用 super.clone()创建克隆。如果按上述方法实现,则将返回 CopyOnWriteArraySet 的实例,而不是子类的实例。因此,这将阻止子类克隆自己。

      The problem here is that it breaks the cloning contract. If a subclass of CopyOnWriteArraySet were to support cloning, calling clone() on that subclass should return an instance of that subclass. The clone() method of the subclass would properly call super.clone() to create the clone. If it were implemented as above, that would return an instance of CopyOnWriteArraySet instead of an instance of the subclass. This would therefore prevent subclasses from being able to clone themselves.

      ConcurrentHashMap 怎么样?它没有任何最终字段。好吧,当时确实如此,因此它恰好遇到了从 clone()方法中更新最终字段的问题。

      What about ConcurrentHashMap? It doesn't have any final fields. Well, it did at the time, so it suffered from exactly the problem of updating final fields from within the clone() method.

      ConcurrentHashMap 的最新版本不再具有最终字段。复制构造函数只是在map参数上调用 putAll ,从而延迟了所有字段的初始化。 clone()方法不能简单地通过克隆,清空所有字段然后调用 putAll()

      Recent versions of ConcurrentHashMap no longer have final fields. The copy constructor simply calls putAll on the map argument, which initializes all the fields lazily. Couldn't the clone() method be implemented simply by cloning, nulling out all the fields, and then calling putAll()?

      这似乎可行,但是我怀疑它与内存模型不符。并非所有字段都是易变的。即使所有字段在重新初始化以指向副本之前都被清空了,其他线程也可能会看到过时的值,该值仍然指向原始映射。也许有办法避免这个问题,但是我怀疑实现者认为提供可复制性是不值得的。

      This seems like it might work, but I suspect it runs afoul of the memory model. Not all of the fields are volatile. Even if all fields were nulled out before being reinitialized to point to copies, other threads might see stale values that still pointed to the original map. There might be ways to avoid this problem, but I suspect the implementors felt that providing cloneability wasn't worth the extra effort.

      这篇关于为什么CopyOnWriteArraySet不实现Cloneable接口,而CopyOnWriteArrayList呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-30 02:48