关于Java可克隆


95

我一直在寻找一些讲解Java的教程Cloneable,但没有获得任何良好的链接,无论如何,Stack Overflow变得越来越明显。

我想知道以下内容:

  1. Cloneable意味着我们可以通过实现Cloneable接口来获得对象的克隆或副本。这样做的优点和缺点是什么?
  2. 如果对象是复合对象,则如何进行递归克隆?

2
相比之下的优缺点是什么?
Galactus 2010年

4
我读到的意思是一类可克隆与不可克隆的优势。不知道该怎么解释:S
allyourcode

Answers:


159

您应该了解的第一件事Cloneable-不要使用它。

Cloneable正确地进行克隆非常困难,而且付出的努力是不值得的。

而不是使用其他一些选项,例如apache-commons SerializationUtils(deep-clone)或BeanUtils(shallow-clone),或仅使用复制构造函数。

有关 Josh Bloch关于with克隆的观点,请参见此处Cloneable,这解释了该方法的许多缺点。(Joshua Bloch是Sun的雇员,并领导了许多Java功能的开发。)


1
我链接了布洛赫(Bloch)的话(而不是引用它们)
博若

3
注意,Block说不要使用Cloneable。他没有说不要使用克隆(或者至少我希望不要使用)。有许多方法可以简单地实现克隆,比使用反射的类SerializationUtils或BeanUtils效率要高得多。请参阅下面的我的帖子作为示例。
查尔斯

定义接口时,复制构造函数的替代方法是什么?只需添加复制方法?
benez '19

@benez我会说是的。由于Java-8您可以static在接口中使用方法,因此只需提供一个static WhatEverTheInterface copy(WhatEverTheInterface initial)?但是我不知道这会给您带来什么,因为您在克隆时从对象复制字段,但是接口仅定义方法。关心解释?
尤金,

40

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

它的作用是更改受保护的Object.clone()方法的行为,该方法将为未实现Cloneable的类抛出CloneNotSupportedException,并为实现该类的成员执行成员方式的浅表复制。

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

在实现自己的clone()时,其想法是从用super.clone()创建的对象开始,该对象必须是正确的类,然后再进行其他任何字段填充,以防浅表副本不是你要。从clone()调用构造函数会带来问题,因为如果子类想要添加自己的其他可克隆逻辑,则这将破坏继承。如果要调用super.clone(),则在这种情况下将获得错误类的对象。

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

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

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

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


12
  1. 克隆会调用一种额外的语言方式来构造对象-无需构造器。
  2. 克隆要求您使用CloneNotSupportedException以某种方式处理-或打扰用于处理它的客户端代码。
  3. 好处很小-您不必手动编写复制构造函数。

因此,请谨慎使用Cloneable。与为正确完成所有事情而需要付出的努力相比,它没有给您带来足够的好处。


正如博佐所说,不要使用Cloneable。而是使用复制构造函数。 javapractices.com/topic/TopicAction.do?Id=12
Bane

@Bane,如果您不知道要克隆的对象的类型,怎么知道要调用哪个类的副本构造函数?
郭富城2010年

@Steve:我不关注。如果要克隆对象,我想您已经知道它是什么类型了-毕竟,您已经准备好要克隆的对象。而且,如果您的对象丢失了它的特定类型,而失去了它的通用类型,那么您是否可以使用简单的“实例”来评估它?
班恩

4
@Bane:假设您有一个对象列表,这些对象都从类型A派生,也许有10种不同的类型。您不知道每个对象的类型。在这种情况下使用instanceof是一个非常糟糕的主意。如果添加其他类型,则每次执行此操作时,都必须添加另一个test实例。而且,如果派生类在另一个包中甚至无法访问,该怎么办?克隆是一种常见的模式。是的,java实现不好,但是围绕它的许多方法都可以正常工作。复制构造函数不是等效操作。
查尔斯

@Charles:由于没有详细的示例,并且缺乏处理此类问题的最新经验,因此我不得不请Bloch。项目#11。它很长,有点辛苦,但是基本上说“尽量避免克隆,复制构造函数是您的朋友”。
贝恩2014年

7

克隆是基本的编程范例。Java可能在许多方面都没有很好地实现它,这一事实丝毫不会减少克隆的需要。而且,很容易实现将起作用的克隆,但是无论您希望它起作用如何,浅层,深层,混合层等等。您甚至可以为函数使用名称克隆,并且如果愿意,可以不实现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,只是使用了相同的函数名。如果您不喜欢该名称,请改用其他名称。


在单独回答您的评论后,我才看到了这一点;我知道您现在要走的路,但是有两件事:1)OP专门询问了使用Cloneable的问题(而不是克隆的一般概念),以及2)您在这里试图在区分复制构造函数方面有些分歧以及克隆的通用概念。您在此处传达的想法是有效的,但从根本上讲,您只是在使用复制构造函数。;)
Bane 2014年

尽管我确实想说我同意您的方法,但是包括一个A#copyMethod(),而不是强迫用户直接调用复制构造函数。
贝恩2014年

5

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

B)默认克隆创建一个浅表副本,除非您的克隆实现对此进行了更改,否则它将保持浅表副本。这可能很难,特别是如果您的班级有最后的领域

博佐是正确的,克隆可能很难正确。复制构造函数/工厂将满足大多数需求。


0

可克隆的缺点是什么?

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

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

因此,当有人创建的克隆时originalObject,假设创建的对象是cloneObject。在这里,originalObjectcloneObject拥有相同的Connection对象参考。

假设说originalObject关闭了该Connection对象,那么cloneObject由于该connection对象在它们之间共享,并且实际上已被关闭,因此will将不起作用originalObject

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

如果对象是复合对象,则如何进行递归克隆?

克隆执行浅拷贝。这意味着原始对象和克隆对象的数据将指向相同的引用/内存。与深度复制相反,将原始对象的内存中的数据复制到克隆对象的内存中。


您的最后一段很困惑。Cloneable不执行复制,而是执行Object.clone。正是“将原始对象的内存中的数据复制到克隆对象的内存中” Object.clone。您需要谈论引用对象的内存来描述深度复制。
aioobe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.