为啥 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()的主要内容,如果未能解决你的问题,请参考以下文章

Intent为啥要实现Cloneable?

Java:Cloneable 接口的基本原理

为啥 Java 8 没有在 `java.util.concurrent.locks.Lock` 接口中添加 `withLock` 默认方法?

java开发——Cloneable接口clone()方法和深浅拷贝

为啥 Java 8 Stream 上没有直接存在 toList() [重复]

浅谈Java中的克隆机制