【问题标题】:Why is Java's Cloneable Interface Not Generic?为什么 Java 的可克隆接口不是通用的?
【发布时间】:2008-12-23 08:55:27
【问题描述】:

Java 5 引入了泛型,它们被添加到java.lang 包中的许多接口中。但是,Cloneable 没有获得泛型。我想知道为什么?


编辑:回复@Jon 和@litb 的回答,以及@Earwicker 的评论,我想Cloneable 可能是:

public interface Cloneable<T> {
    public T clone();
}

这里T clone(); 覆盖Object.clone(),给它一个协变类型。我相信这仍然是向后兼容的并增加了类型安全性。那为什么不呢?


编辑 2: 从下面的答案(和 cmets)中可以看出,上面建议的界面会破坏向后兼容性。由于Object.clone()protected,在接口中重写它会强制所有实现者提供public 实现,而类设计者可能不希望这样做(即他们可能会选择保留它protected)。

【问题讨论】:

  • 这个问题的答案(由 Skeet 和 libt 给出)只是提出了进一步的问题,例如为什么 Cloneable 不包含 clone 方法?
  • 引用关于该主题的 Sun 错误,即“迷失在时间的迷雾中”(不,可能不是准确的引用,因为我没有打开错误)

标签: java generics


【解决方案1】:

Cloneable 接口不包含任何成员。将其设为通用有什么意义?

(如果 Cloneable 包含 clone() 方法,这将是有意义的 - 但这是在 java.lang.Object 中声明的。)

编辑:clone() 在 java.lang.Object 中,因为它有一个实现(逐个字段复制)。更好的设计是将 .NET 的 MemberwiseClone() 之类的东西作为 Object 中的受保护方法,然后在接口本身中使用公共 clone() 方法。我不知道为什么没有选择那个设计。

(在 .NET 中,ICloneable 不是泛型,因为它在泛型之前就存在 - .NET 泛型的不同性质会阻止以前的非泛型类型变为泛型。)

然而,在 Java 和 .NET 中,“普通”克隆 API 通常被认为是一件坏事,因为它没有说明应该执行什么深度的克隆。

【讨论】:

  • 感谢您的回复。我已经编辑了我的问题以澄清我的想法。
  • 无法更改 java.lang.Object 中的公共 clone() 方法以实现向后兼容性。我对此很好。但我看不出我建议对界面进行的修改会如何有害。我认为它是向后兼容的,并且类型安全。你怎么看?
  • Hosam,克隆在 java.lang.Object 中受到保护。所以想要被外部人员克隆的类必须覆盖它并明确地公开它。我认为乔恩谈论克隆的名字。 c# 的 MemberwiseClone 清楚地说明了发生了什么。
  • 我同意 MemberwiseClone 是一个更好的名字。并感谢您提醒我克隆受到保护。我更倾向于认为实现 Cloneable 的类会提供公共 clone() 方法,但这不是必需的。所以我的界面确实会破坏向后兼容性。谢谢!
【解决方案2】:

如果CloneableCloneable&lt;T&gt;,那么就不可能这样做:

class A extends B implements Cloneable<A>

class B implements Cloneable<B>

不能用不同的参数实现同一个接口,类A实现Cloneable&lt;A&gt;Cloneable&lt;B&gt;

【讨论】:

  • 我正要尝试执行 OP 的建议,感谢您为我节省了 2 小时。
【解决方案3】:

java.lang.Cloneable 是一个标记接口。这仅是为了使 Object.clone 可以抛出异常以指示类不支持克隆,例如使用示例

if(!(this instanceof Cloneable))
    throw...;

来自文档:

类 Object 的方法克隆 执行特定的克隆操作。 首先,如果这个对象的类 没有实现接口 可克隆,然后是 抛出 CloneNotSupportedException。

它没有任何方法。让它成为一个通用的不会有任何用处。

【讨论】:

  • 是的,我同意。但是,如果它是我在问题编辑中所写的形式,这个标记界面会不会更有用?
  • 对不起,我不知道。可能是向后兼容性(毕竟从一开始就没有泛型),或者是为了方便(对象将正确复制原语(当然很深))。我想我们将不得不等待飞碟对此作出解释。我相信他会知道的。
【解决方案4】:

这并没有解决您的理论问题,而是处理实际案例:

Java 5 具有协变返回值,并允许扩大方法的访问范围。因此,您可以在子类中适当地更改 clone() 的签名。

public class MyClass implements Cloneable {
    public MyClass clone(){ /* do the right stuff */ }
}

MyClass.clone()Object.clone() 的正确覆盖,您不必编写强制转换。

【讨论】:

  • 它甚至适用于泛型类。但是有一个问题,如果您想在界面中使用它,您需要明确公开克隆,即使某些 IDE 告诉您不必这样做(与您交谈 IntelliJ IDEA!)
【解决方案5】:

作为一个半相关的思考问题:创建一个将 clone() 公开为公共成员的接口(称为ReallyCloneable)有用吗?

我的论点是不,它不会。可克隆性与具体的类实现密切相关。我想不出一个我可能会说“我有一个任意对象,我想要它的副本”的用例。直接的问题是:你为什么想要那个副本?

而典型的答案是,您可以在不影响原件的情况下修改副本。但是,要做到这一点,你需要知道你持有什么类型的对象,否则你怎么知道调用什么来修改它?如果你知道这一点,你就会知道它是否提供了一个公共的 clone() 方法。

但是,如果您正在对一个接口(例如 List)进行编程,该怎么办?智能(递归)克隆(相对于字节级对象数据复制)对于 Collections 框架将非常有用。但是可能有一些集合(比如由数据库支持的集合)不支持这样的操作,所以你不能要求 List 公开一个公共的 clone()。这促使您实例化自己的具体 List 实现以复制源 List 的内容。

【讨论】:

  • 非通用克隆不会非常有用,但在 .net 中,IClonable&lt;List&lt;Thingie&gt;&gt; 可能有用,特别是如果它还有一个类型为 List&lt;Thingie&gt; 的成员 Self。深语义和浅语义不应该有歧义:在 ICloneable&lt;List&lt;Thingie&gt;&gt; 上调用 Clone 应该返回一个实现 ICloneable&lt;List&lt;Thingie&gt;&gt; 的新事物,并且其 Self 方法包含与原始相同的 Thingie 引用(如果 @ 987654329@ 是一个类)或 Thingie 具有与原始字段相同字段的结构(如果它是一个结构)。
猜你喜欢
  • 2012-07-13
  • 1970-01-01
  • 1970-01-01
  • 2016-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-07
相关资源
最近更新 更多