为啥 Java 8 的 Cloneable 中没有默认的 clone()
Posted
技术标签:
【中文标题】为啥 Java 8 的 Cloneable 中没有默认的 clone()【英文标题】:Why no default clone() in Cloneable in Java 8为什么 Java 8 的 Cloneable 中没有默认的 clone() 【发布时间】:2016-03-11 08:57:39 【问题描述】:Java 中的Cloneable
天生就被破坏了。具体来说,我对接口的最大问题是它需要一个不定义方法本身的方法行为。因此,如果遍历 Cloneable
列表,您必须使用反射来访问其定义的行为。但是,在 Java 8 中,我们现在有了默认方法,现在我问为什么 Cloneable
中没有默认的 clone()
方法。
我理解为什么interfaces cannot default Object methods,但是,这是一个明确的设计决定,因此可以例外。
我有点想弃用 Object.clone()
并将其内部代码更改为:
if(this instanceof Cloneable)
return ((Cloneable) this).clone();
else
throw new CloneNotSupportedException();
继续前进,让clone()
成为Cloneable
的默认方法。这并不能真正解决 clone()
仍然很容易被错误实现的问题,但这本身就是另一个讨论。
据我所知,此更改将完全向后兼容:
-
当前覆盖
clone()
但未实现 Cloneable
(为什么?!)的类在技术上仍然可以(即使在功能上不可能,但这与以前没有什么不同)。
当前覆盖 clone()
但确实实现了 Cloneable
的类在其实现中仍会发挥相同的作用。
当前未覆盖clone()
,但实现Cloneable
(为什么?!)的类现在将遵循规范,即使它在功能上完全不正确。
那些使用反射并引用 Object.clone()
的函数仍然可以正常工作。
super.clone()
即使引用 Object.clone()
,其功能仍然相同。
更不用说这会解决Cloneable
的一个巨大问题。虽然繁琐并且仍然很容易错误地实现,但它可以解决接口的一个巨大的面向对象问题。
我能看到的唯一问题是实现Cloneable
的那些没有义务覆盖clone()
,但这与以前没有什么不同。
这是否在内部讨论过,但从未实现?如果是这样,为什么?如果是因为接口不能默认 Object 方法的原因,那么在这种情况下进行异常处理是否有意义,因为所有继承 Cloneable
的对象都期待 clone()
反正?
【问题讨论】:
我记得在某处(来自官方来源)读到Cloneable
已损坏,甚至不值得修复。但是您的默认方法的问题之一是使用this
在那里很棘手。
基本上,@Tunaki 所说的是正确的。这种跳圈的复杂性并不存在。我们选择将 时间、精力、复杂性 预算投入到产生更多价值的其他领域。
创建这样的default
方法是没有意义的。如果java.lang.Object
中有一个方法和interface
中有同名和签名的default
方法,则java.lang.Object
中声明的方法仍然会因不合格的调用而获胜。如果java.lang.Object
中的方法保持protected
,则所有Cloneable
实现都将强制将该方法重新声明为public
,而将其更改为public
仍需要调整现有实现。换句话说,结果与在Cloneable
接口中定义abstract clone()
方法没有区别。
它不完全兼容 - 它会在类具有非公共 clone
方法的地方中断,因为它会尝试用 protected
方法覆盖 public
方法(例如如果需要克隆一个类,但只能克隆它自己)
【参考方案1】:
你的问题有点宽泛,更多的是讨论,但我可以对这个问题有所了解。
在 Effective Java™ 中,Joshua Bloch 对这种情况进行了相当详细的说明。他以Cloneable
背后的一些历史开场@
Cloneable 接口旨在作为对象的 mixin 接口 宣传他们允许克隆。不幸的是,它无法达到这个目的。它的主要缺陷是它缺少一个克隆方法,而对象的克隆方法是受保护的。不借助反射,你不能仅仅因为一个对象实现了 Cloneable 就调用它的 clone 方法。
继续推理
[Cloneable] 决定了 Object 的受保护克隆实现的行为:如果一个类实现了 Cloneable,则 Object 的 clone 方法会返回该对象的逐个字段副本...被效仿。通常,实现一个接口说明了一个类可以为它的客户做什么。对于 Cloneable,它会修改超类上受保护方法的行为。
和
如果实现 Cloneable 接口要对类产生任何影响,则 类及其所有超类必须服从一个相当复杂的、不可执行的和 记录薄弱的协议。由此产生的机制是语言外的:它创建一个对象而不调用构造函数。
这里有很多细节,但要注意一个问题:
克隆架构与引用可变对象的 final 字段的正常使用不兼容。
我认为这足以说明在接口中使用default
方法进行克隆。正确实现它会非常复杂。
【讨论】:
【参考方案2】:我的体验可能离主流还很远,但是我使用clone()
,并且支持Cloneable
目前的设计。可能将它作为注释会更好,但是Cloneable
早在注释之前就出现了。我的观点是Cloneable
是一个低级的东西,没有人应该做类似obj instanceof Cloneable
的事情。如果您在某些业务逻辑中使用Cloneable
,最好声明您自己的接口或抽象类,将clone()
公开并在所有业务逻辑对象中实现它。有时您可能实际上不想公开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
中使用 public clone()
会不必要地污染类的公共接口。
我使用Cloneable
的另一种情况是Spliterator.trySplit()
的实现。请参阅简单拆分器的implementation,它返回给定数量的常量对象。它有四个特化(对象、整数、长整数和双精度数),但感谢clone()
,我只能在超类中实现一次trySplit()
。再说一遍,我不想暴露clone()
,我只想自己用。
总之,在Cloneable
接口中没有clone()
方法实际上更灵活,因为它允许我决定是否要公开它。
【讨论】:
@Holger,这是另一种与protected clone()
不同的采用参数的方法以上是关于为啥 Java 8 的 Cloneable 中没有默认的 clone()的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Java 8 没有在 `java.util.concurrent.locks.Lock` 接口中添加 `withLock` 默认方法?
java开发——Cloneable接口clone()方法和深浅拷贝