众所周知,Cloneable
Java 中的接口已损坏。原因有很多,我就不说了;其他人已经这样做了。这也是Java架构师自己的立场。
因此,我的问题是:为什么还没有被弃用?如果核心 Java 团队决定它被破坏了,那么他们一定也考虑过弃用。他们反对这样做的原因是什么(在 Java 8 中它仍然没有被弃用)?
1997 年向 Java Bug Database提交了一个关于添加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 接口中添加额外的文档,以更好地描述它的使用方式,并为实现者描述“最佳实践”。
因此,尽管这与 deprecated 没有直接关系,但不使 Cloneable “弃用”的原因是 Technical Review Comitee 认为修改现有文档足以使该接口变得有用。他们就这样做了。在 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 方法。因此,不可能仅凭借实现该接口的事实来克隆对象。即使以反射方式调用 clone 方法,也不能保证它会成功。
Cloneable
“为什么不被弃用?”的简短回答 (或者实际上,为什么不X
被弃用,对于任何X
)是没有太多关注来弃用它们。
大多数最近被弃用的东西都被弃用了,因为有一个特定的计划来删除它们。例如,LogManageraddPropertyChangeListener
的和removePropertyChangeListener
方法在 Java SE 8 中被弃用,目的是在 Java SE 9 中删除它们。(原因是它们不必要地使模块相互依赖复杂化。)事实上,这些 API 已经从早期的 JDK 9开发中删除构建。(请注意,类似的属性更改侦听器调用也已从 中删除;请参阅JDK-8029806。)Pack200
Cloneable
对于和不存在类似的计划Object.clone()
。
更长的答案将涉及讨论进一步的问题,例如人们可能期望这些 API 会发生什么,如果它们被弃用,平台会产生哪些成本或收益,以及当 API 被弃用时与开发人员沟通的内容。我在最近的 JavaOne 演讲“债务与弃用”中探讨了这个主题。(该链接上提供的幻灯片;视频在这里。)事实证明,JDK 本身在其弃用的使用上并不是很一致。它被用来表示几种不同的事物,例如,
这是危险的,您应该注意使用它的风险(例如:Thread.stop()
、Thread.resume()
和Thread.suspend()
)。
这将在未来的版本中删除
这已经过时了,您最好使用不同的方法(例如: 中的许多方法java.util.Date
)
所有这些都是不同的含义,它们的不同子集适用于已弃用的不同事物。其中一些子集适用于不被弃用的东西(但可能应该被弃用)。
Cloneable
并且Object.clone()
在某种意义上是“损坏的”,因为它们存在设计缺陷并且难以正确使用。但是,clone()
它仍然是复制数组的最佳方式,并且克隆对于复制经过仔细实现的类的实例的用处有限。删除克隆将是一个不兼容的更改,会破坏很多东西。可以以不同的方式重新实现克隆操作,但它可能会比Object.clone()
.
然而,对于大多数事情来说,复制构造函数比克隆更可取。因此,也许将其标记Cloneable
为“过时”或“已取代”或类似的东西是合适的。这将告诉开发人员他们可能想要寻找其他地方,但这并不意味着克隆机制可能会在未来的版本中被删除。不幸的是,不存在这样的标记。
就目前情况而言,“弃用”似乎意味着最终删除——尽管事实上已经删除了极少数已弃用的功能——因此克隆机制似乎没有必要弃用。也许将来可以应用替代标记,指导开发人员使用替代机制。
更新
我在错误报告中添加了一些额外的历史记录。早期的 JVM 实现者和 JVM 规范的合著者 Frank Yellin 发表了一些评论,以回应另一个答案中引用的 TRC 建议中的“迷失在时间的迷雾中”的评论。我在这里引用了相关部分;完整的消息在错误报告中。
Cloneable 没有方法,原因与 Serializable 没有方法相同。Cloneable 表示类的属性,而不是具体说明类支持的方法。
在反射之前,我们需要一个本地方法来制作对象的浅拷贝。因此 Object.clone() 诞生了。很明显,许多类都想重写这个方法,而且不是每个类都想被克隆。因此 Cloneable 的诞生是为了表明程序员的意图。
所以,简而言之。Cloneable 的目的不是表明您有一个公共的 clone() 方法。这是为了表明您愿意使用 Object.clone() 进行克隆,并且由实现决定是否将 clone() 公开。
为什么它还没有被弃用?
因为 JCP 认为这样做不合适,而且可能永远不会这样做。问他们。你问错地方了。
将这个东西保留在 Java API 中的原因是什么
由于向后兼容的要求,没有人会从 Java API 中删除任何内容。上一次发生是 1996/7 年 AWT 事件模型在 1.0 和 1.1 之间的变化。