102

我想知道以下内容:

  1. Cloneable意味着我们可以通过实现Cloneable接口来克隆或复制对象。这样做有什么好处和坏处?
  2. 如果对象是复合对象,递归克隆如何发生?
4

6 回答 6

166

您应该知道的第一件事Cloneable是 - 不要使用它。

正确的克隆很难实现Cloneable,付出的努力是不值得的。

取而代之的是使用其他一些选项,例如 apache-commons SerializationUtils(deep-clone) 或BeanUtils(shallow-clone),或者简单地使用复制构造函数。

请参阅此处了解 Josh Bloch 关于克隆的观点Cloneable,这解释了该方法的许多缺点。(Joshua Bloch是 Sun 的员工,领导了众多 Java 特性的开发。)

于 2010-11-02T20:40:09.890 回答
41

不幸的是,Cloneable 本身只是一个标记接口,即:它没有定义 clone() 方法。

所做的是更改受保护的 Object.clone() 方法的行为,该方法将为未实现 Cloneable 的类抛出 CloneNotSupportedException,并为实现的类执行逐个成员的浅拷贝。

即使这是您正在寻找的行为,您仍然需要实现自己的 clone() 方法才能将其公开。

在实现你自己的 clone() 时,想法是从 super.clone() 创建的对象开始,它保证是正确的类,然后做任何额外的字段填充,以防浅拷贝不是什么你要。从 clone() 调用构造函数会有问题,因为如果子类想要添加自己的额外可克隆逻辑,这会破坏继承;如果要调用 super.clone() 在这种情况下它会得到一个错误类的对象。

但是,这种方法绕过了可能在构造函数中定义的任何逻辑,这可能会出现问题。

另一个问题是任何忘记覆盖 clone() 的子类都将自动继承默认的浅拷贝,这可能不是您想要的可变状态(现在将在源和副本之间共享)。

由于这些原因,大多数开发人员不使用 Cloneable,而是简单地实现一个复制构造函数。

有关 Cloneable 的更多信息和潜在陷阱,我强烈推荐 Joshua Bloch 的《Effective Java》一书

于 2010-11-02T20:50:55.377 回答
13
  1. 克隆调用了一种额外的语言构造对象的方式——没有构造函数。
  2. 克隆要求您以某种方式处理 CloneNotSupportedException - 或者打扰客户端代码来处理它。
  3. 好处很小——您不必手动编写复制构造函数。

因此,请明智地使用 Cloneable。与您为做好每一件事所需要付出的努力相比,它并没有给您带来足够的好处。

于 2010-11-02T20:45:26.607 回答
8

克隆是一种基本的编程范式。Java 可能在许多方面实现得很糟糕,这一事实并没有减少对克隆的需求。而且,很容易实现克隆,无论你希望它如何工作,浅的、深的、混合的,等等。如果您愿意,您甚至可以为该函数使用名称 clone 而无需实现 Cloneable。

假设我有 A、B 和 C 类,其中 B 和 C 派生自 A。如果我有一个 A 类型的对象列表,如下所示:

ArrayList<A> list1;

现在,该列表可以包含 A、B 或 C 类型的对象。您不知道这些对象是什么类型。因此,您不能像这样复制列表:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

如果对象实际上是 B 或 C 类型,您将不会得到正确的副本。而且,如果 A 是抽象的怎么办?现在,有人提出了这样的建议:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

这是一个非常非常糟糕的主意。如果添加新的派生类型怎么办?如果 B 或 C 在另一个包中并且您在此类中无权访问它们怎么办?

你想做的是这样的:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

很多人已经指出为什么克隆的基本 Java 实现存在问题。但是,它很容易通过这种方式克服:

A类:

public A clone() {
    return new A(this);
}

B类:

@Override
public B clone() {
    return new B(this);
}

C类:

@Override
public C clone() {
    return new C(this):
}

我没有实现 Cloneable,只是使用相同的函数名。如果你不喜欢它,就给它取个名字。

于 2014-02-15T22:13:48.490 回答
5

A)与复制构造函数相比,克隆并没有太多优势。最大的可能是创建完全相同动态类型的新对象的能力(假设声明的类型是可克隆的并且具有公共克隆方法)。

B)默认克隆创建一个浅拷贝,除非你的克隆实现改变它,否则它将保持一个浅拷贝。这可能很困难,特别是如果您的课程有最终字段

Bozho 是对的,克隆很难做到正确。复制构造函数/工厂将满足大多数需求。

于 2010-11-02T20:48:56.040 回答
0

可克隆的缺点是什么?

如果要复制的对象具有组合,则克隆非常危险。在这种情况下,您需要考虑以下可能的副作用,因为克隆会创建浅拷贝:

假设您有一个对象来处理与数据库相关的操作。说,该对象具有Connection对象作为属性之一。

因此,当有人创建 的克隆时originalObject,正在创建的对象,比如说,cloneObject。这里originalObjectcloneObject持有相同的Connection对象引用。

假设originalObject关闭Connection对象,所以现在cloneObject将不起作用,因为connection对象在它们之间共享并且它实际上是由originalObject.

如果假设您要克隆具有 IOStream 作为属性的对象,则可能会出现类似的问题。

如果对象是复合对象,递归克隆如何发生?

Cloneable 执行浅拷贝。意思是原始对象和克隆对象的数据将指向相同的引用/内存。相反,在深拷贝的情况下,原始对象内存中的数据被复制到克隆对象的内存中。

于 2016-03-13T05:43:23.293 回答