为什么Java 8中的Cloneable中没有默认的clone()

Java中的可Cloneable本质上是破碎的。 具体来说,我对界面的最大问题是它需要一种不定义方法本身的方法行为。 因此,如果遍历Cloneable列表,则必须使用reflection来访问其定义的行为。 但是,在Java 8中,我们现在有了默认方法,现在我问为什么Cloneable没有默认的clone()方法。

我理解为什么接口不能默认Object方法 ,但是,这是一个明确的设计决策,因此可以做出exception。

我有点想象弃用Object.clone()并将其内部代码更改为:

 if(this instanceof Cloneable) { return ((Cloneable) this).clone(); } else { throw new CloneNotSupportedException(); } 

继续使用任何魔法使clone()Cloneable作为默认方法。 这并没有真正解决clone()仍然可以很容易地被错误地实现,但这本身就是另一个讨论。

据我所知,这种变化将完全向后兼容:

  1. 当前覆盖clone()但没有实现Cloneable (WHY ?!)的类在技术上仍然可以接受(即使在function上不可能,但这与以前没有什么不同)。
  2. 当前覆盖clone()但实现Cloneable的类在其实现上仍将起相同的作用。
  3. 当前没有覆盖clone()但是实现了Cloneable (WHY ?!)的类现在将遵循规范,即使它在function上并不完全正确。
  4. 使用reflection并引用Object.clone()那些仍然可以在function上工作。
  5. 即使它引用了Object.clone() super.clone()在function上仍然是相同的。

更不用说这将解决Cloneable一个巨大问题。 虽然繁琐且仍然容易错误地实现,但它将解决界面的巨大面向对象问题。

我能看到的唯一问题是那些实现Cloneable人没有义务覆盖clone() ,但这与之前没有什么不同。

这已在内部进行过讨论,但从未取得成果吗? 如果是这样,为什么? 如果是因为接口不能默认使用Object方法,那么在这种情况下做出exception是否有意义,因为inheritanceCloneable所有对象都期待clone()

你的问题有点广泛而且更多的讨论,但我可以对这个问题有所了解。

Effective Java™中 ,Joshua Bloch给出了相应的情况。 他打开了Cloneable背后的一些历史

Cloneable接口旨在作为对象的mixin接口,以宣传它们允许克隆。 不幸的是,它没有达到这个目的。 它的主要缺陷是缺少克隆方法,而Object的克隆方法受到保护。 如果不依靠reflection,就不能仅仅因为它实现了Cloneable而在对象上调用clone方法。

并继续推理

[Cloneable]确定Object的受保护克隆实现的行为:如果一个类实现了Cloneable,则Object的clone方法返回该对象的逐个字段副本……这是一个非常非典型的接口使用,而不是一个被模拟的接口。 通常,实现接口会说明类可以为其客户做些什么。 在Cloneable的情况下,它修改了超类上受保护方法的行为。

如果实现Cloneable接口对类有任何影响,那么该类及其所有超类必须遵守相当复杂,不可执行且记录细则的协议。 结果机制是extralinguistic:它创建一个对象而不调用构造函数。

这里有很多细节,但要注意一个问题:

克隆体系结构与引用可变对象的最终字段的正常使用不兼容。

我认为这足以说明在接口中使用default方法进行克隆。 正确实施它会非常复杂。

我的经验可能远不是主流,但我使用clone()并支持当前的Cloneable设计。 可能最好将它作为注释,但Cloneable早在注释之前出现。 我的观点是, Cloneable是一个低级别的东西,没有人应该做像obj instanceof Cloneable这样的东西。 如果您在某些业务逻辑中使用Cloneable ,那么声明自己的接口或抽象类会更好,它将clone()暴露给public并在所有业务逻辑对象中实现它。 有时你可能不希望实际暴露clone() ,但是在内部创建你自己的方法使用clone()

例如,假设您具有命名对象的层次结构,其中名称在构造后无法更改,但您希望允许使用新名称克隆它们。 您可以创建一些这样的抽象类:

 public abstract class NamedObject implements Cloneable { private String name; protected NamedObject(String name) { this.name = name; } public final String getName() { return name; } public NamedObject clone(String newName) { try { NamedObject clone = (NamedObject)super.clone(); clone.name = newName; return clone; } catch(CloneNotSupportedException ex) { throw new AssertionError(); } } } 

即使您实现了Cloneable ,也希望使用clone() ,但不希望公开它。 相反,您提供了另一种允许使用其他名称进行克隆的方法。 因此,在Cloneable使用公共clone()会不必要地污染类的公共接口。

我使用Cloneable另一种情况是Spliterator.trySplit()的实现。 查看简单spliterator的实现 ,它返回给定数量的常量对象。 它有四个特化(对象,整数,长整数和双精度),但是由于clone()我只能在超类中实现一次trySplit() 。 同样,我不想暴露clone() ,我只是想自己使用它。

总而言之, Cloneable接口中没有clone()方法实际上更灵活,因为它允许我决定是否要公开它。