为什么不推荐使用Cloneable?


139

众所周知,CloneableJava 中的接口已损坏。造成这种情况的原因很多,我将不再赘述。其他人已经做到了。这也是Java架构师本身的立场。

因此,我的问题是:为什么还不被弃用?如果核心Java团队已确定它已损坏,那么他们还必须考虑过时。他们反对这样做的原因是什么(在Java 8中仍然不推荐使用)?


41
这个问题不是“主要基于意见”的,因为许多人显然认为有权判断。那些只对原因有意见的人根本没有资格回答。但是,的确,您在这里获得权威答案的机会很小。的确,您的问题不是关于您可以解决的问题,因此,它至少是边缘问题。
Marko Topolnik 2014年

6
@MarkoTopolnik我同意世界上有些人可以提供权威的答案,但是我不认为这是我们在这里进行的测试。封闭原因指出“对该问题的答案几乎完全基于观点”。我怀疑这里会是这样,除非我们非常幸运。
邓肯·琼斯

2
这是从Oracle不赞成使用的“如何和何时” ...(docs.oracle.com/javase/6/docs/technotes/guides/javadoc/…)可克隆的接口可能属于“ 笨拙或效率低下”的情况,但是它非常开放的意见。
Maxx

8
@Duncan我仍然不认为这是公平的转嫁判断问题根据我了解的缺乏纪律的假设应答者的一部分。如果用户不知道被问到的原因,则他无权滥用回答工具就此事发表意见。
Marko Topolnik

4
@lexicore是的,没错---你可以打赌他们已经充分考虑该选项并暗示必须有强大的理由不贬低它。他们自己的批评Cloneable是众所周知的。
Marko Topolnik

Answers:


120

有一个错误在1997年提交给Java的错误数据库有关添加clone()方法Cloneable,所以将不再是无用的。它以“无法解决”的决议关闭,理由如下:

Sun的技术审查委员会(TRC)仔细考虑了此问题,建议不要采取任何措施,除非改进当前Cloneable接口的文档。这是建议的全文:

现有的Java对象克隆API存在问题。在java.lang.Object上有一个受保护的“克隆”方法,在接口java.lang.Cloneable中也有。目的是,如果一个类希望允许其他人克隆它,则它应支持Cloneable接口,并使用公共克隆方法覆盖默认的受保护克隆方法。不幸的是,由于在时间的迷雾中很容易迷失原因,Cloneable接口没有定义克隆方法。

这种结合导致相当多的混乱。一些类声称支持Cloneable,但偶然忘记支持clone方法。开发人员对于Cloneable应该如何工作以及克隆应该做什么感到困惑。

不幸的是,向Cloneable添加“克隆”方法将是不兼容的更改。它不会破坏二进制兼容性,但是会破坏源兼容性。轶事证据表明,实际上,在许多情况下,类支持Cloneable接口,但无法提供公共克隆方法。经过讨论,TRC一致建议我们不要修改现有的Cloneable接口,因为它会影响兼容性。

另一种建议是添加一个新接口java.lang.PubliclyCloneable,以反映Cloneable的最初预期目的。TRC建议以5到2的多数反对。主要担心的是,这会给已经混乱的画面增加更多的混乱(包括拼写混乱!)。

TRC一致建议,我们应该在现有的Cloneable接口中添加其他文档,以更好地描述其用途以及为实现者描述“最佳实践”。

因此,尽管这并不是直接弃用,但不使Cloneable成为“弃用”的原因是技术评论委员会决定修改现有文档足以使此接口有用。因此,他们做到了。直到Java 1.4为止,Cloneable文档记录如下:

一个类实现Cloneable接口,以向Object.clone()方法指示该方法为该类的实例进行逐域复制是合法的。

尝试克隆未实现Cloneable接口的实例会导致抛出CloneNotSupportedException异常。

接口Cloneable不声明任何方法。

从Java 1.4(2002年2月发布)到最新版本(Java 8),它看起来像这样:

一个类实现Cloneable接口,以向Object.clone()方法指示该方法为该类的实例进行逐域复制是合法的。在未实现Cloneable接口的实例上调用Object的clone方法会导致抛出CloneNotSupportedException异常。

按照约定,实现此接口的类应使用公共方法重写Object.clone(受保护的)。有关覆盖此方法的详细信息,请参见Object.clone()。

请注意,此接口不包含clone方法。因此,不可能仅凭借对象实现此接口的事实来克隆对象。即使克隆方法是反射式调用的,也不能保证它会成功。


3
而且您知道为什么首先使用该clone方法Object吗?
njzk2 2014年

8
@ njzk2这是该机制的重要组成部分-它是执行底层语言学魔术的方法,可以一点一点地复制对象图像。
Marko Topolnik

3
@Unheilig魔术是您不能使用复制构造函数复制的东西:Object#clone()生成与原始类相同的类的实例,而在编译时不知道该类。
Marko Topolnik

4
@AVolpe:这不能解决答案“在类支持Cloneable接口但无法提供公共克隆方法的地方”中提到的源不兼容性。特别是,提供非公共克隆方法的类将被破坏。
Louis Wasserman

2
美好的历史。这是指向错误数据库的直接链接。我在那里添加了更多历史记录,并在答案中引用了其中的部分内容。
斯图尔特·马克斯

64

简短的回答“为什么不Cloneable被弃用?” (或者实际上,为什么不X被弃用X)是因为对它们的弃用没有引起太多关注。

最近已经弃用的大多数东西均已弃用,因为有特定的计划将其删除。例如,在Java SE 8不推荐使用LogManageraddPropertyChangeListenerremovePropertyChangeListener方法,目的是在Java SE 9中将其删除。(原因是它们不必要地增加了模块相互依赖性。)确实,这些API 已从早期JDK 9开发中删除。建立。(请注意,类似的属性更改侦听器调用也已从中删除;请参阅JDK-8029806。)Pack200

没有针对Cloneable和的类似计划Object.clone()

更长的答案将涉及讨论其他问题,例如这些API可能会发生什么,如果不赞成使用这些平台会给平台带来哪些成本或收益,以及不赞成使用API​​会传达给开发人员的信息。我在最近的JavaOne演讲Debt and Deprecation中探讨了这个主题。(可以在该链接上找到幻灯片;请参见此处的视频。)事实证明,JDK本身在弃用方面的使用并不十分一致。它曾经被用来表示几种不同的事物,例如,

  • 这是危险的,你应该知道使用它的风险(例如:Thread.stop()Thread.resume(),和Thread.suspend())。

  • 在将来的版本中将删除它

  • 这已经过时,建议您使用其他方法(例如:中的许多方法java.util.Date

所有这些都是不同的含义,它们的不同子集适用于已弃用的不同事物。其中的一些子集适用于未弃用的事物(但可能应弃用)。

Cloneable并且Object.clone()在这个意义上,他们有设计缺陷,且难以正确使用“坏”。但是,clone()仍然是复制数组的最佳方法,而克隆在复制经过仔细实现的类的实例方面的作用有限。删除克隆将是一个不兼容的更改,它将破坏很多事情。克隆操作可以以其他方式重新实现,但可能比慢Object.clone()

但是,对于大多数情况而言,复制构造函数比克隆更可取。因此,将其标记Cloneable为“过时”或“已取代”或类似的内容将是适当的。这将告诉开发人员他们可能想在其他地方查看,但这并不表示克隆机制可能会在将来的版本中删除。不幸的是,不存在这样的标记。

顺便说一句,“弃用”似乎意味着最终删除-尽管事实上已经删除了很少数量的弃用功能-因此克隆机制似乎并不需要弃用。也许将来可以使用替代标记,指导开发人员改用替代机制。

更新

我已在错误报告中添加了一些其他历史记录。JVM的早期实现者和JVM规范的合著者Frank Yellin针对其他答案中引用的TRC建议中的“迷失了时间”发表了一些评论。我在这里引用了相关部分;完整的消息在错误报告中。

出于与Serializable相同的原因,Cloneable没有方法。Cloneable表示该类的属性,而不是专门说明该类支持的方法。

进行反射之前,我们需要一个本机方法来制作对象的浅表副本。因此Object.clone()诞生了。同样很明显,许多类都希望覆盖此方法,并且并非每个类都希望被克隆。因此,Cloneable的诞生表明了程序员的意图。

简而言之。Cloneable的目的并不是要表明您具有公共的clone()方法。这表明您愿意使用Object.clone()进行克隆,这取决于实现决定是否将clone()公开。


3
先生,你有一个好的答案。我特别喜欢您Object.clone(),不仅仅是因为其他所有人都想扔在火上,而是您愿意推理并提出其中的好处。
icza

2
但是,clone()仍然是复制数组的最佳方法,而克隆在复制经过仔细实现的类的实例方面的作用有限。我对6428387的修复印象深刻,所有代码路径(克隆,new / arrayCopy,Arrays.copyOf)都具有相同的内在函数。最近有什么变化吗?
bestsss 2014年

2
@bestsss我认为array.clone()不一定比任何替代方法都要快。从API角度来看,这是复制数组的最简洁方法。Arrays.copyOf(array, newlen)距离很近,但它需要一个length参数,如果您不更改长度,则此参数是多余的。
Stuart Marks 2014年

2
@Holger是的,据我们所知,这是自1.1以来首次对API的实际删除。还要注意,即使我们同意Thread.suspend()Thread.stop()(no-arg)是危险的,它们也可能不会被删除-或更改为无条件抛出异常-因为人们实际上在使用它们!大概他们愿意承担风险。属性更改侦听器的缓解因素之一是,它们很少被使用,因此删除它们的影响很小。
斯图尔特·马克

2
@Holger从概念上讲,java.beans可以独立于@Holger ,java.desktop因为bean只是属性的库API。不幸的是,如果您深入研究bean API,那么对AWT的依赖就很多。实现还有更多。当然,也许可以解压缩它们,但是这样做比将bean的日志记录解开要耗费更多的精力。整个模块化工作都是为了解决这个问题。无疑可以做更多的事情,但是拼图需要更长的时间。
Stuart Marks 2014年

-1

为什么它不被弃用呢?

因为JCP认为不适合这样做,而且可能永远也不会这样做。问他们。您在错误的地方问。

在Java API中保留此内容的原因是什么?

由于向后兼容性的要求,没有人会从Java API中删除任何内容。上一次发生的事件是1996/7年AWT事件模型在1.0和1.1之间进行了更改。


17
他们确实Thread.stop(Throwable)通过更改其合同来删除(有效地)以始终扔给UnsupportedOperationException调用方(而不是目标线程!)。
Marko Topolnik

22
大约在同一时间发生了什么?Thread.stop(Throwable)的功能删除在Java 8中发生了。无论如何,“询问它们”的无条件建议是错误的,因为如今首席Java架构师本人是Stack Overflow的活跃成员。除了与Streams有关的问题外,他只是没有回答任何其他问题。
Marko Topolnik

13
此外,OP的问题不是关于移除,而是关于弃用,并且显然弃用一直在发生。
Marko Topolnik 2014年

17
@EJP我不问是否Cloneable将从Java API中删除。我在问为什么不去练习。我认为这是一个完美的提问场所。
花王

5
@VaheHarutyunyan感谢您的大声疾呼,但我不是Java架构师。我是Oracle JDK小组的工程师,该小组负责维护这些工作。
斯图尔特(Stuart Marks)2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.