clone()vs copy constructor vs factory方法?

时间:2009-07-09 19:52:57

标签: java clone

我在Java上实现了克隆()的快速谷歌,发现: http://www.javapractices.com/topic/TopicAction.do?Id=71

它有以下评论:

  

复制构造函数和静态工厂方法提供了克隆的替代方法,并且更容易实现。

我想做的就是制作一份深刻的副本。实现clone()似乎很有意义,但这篇谷歌排名很高的文章让我有点害怕。

以下是我注意到的问题:

复制构造函数不适用于Generics。

这是一些无法编译的伪代码。

public class MyClass<T>{
   ..
   public void copyData(T data){
       T copy=new T(data);//This isn't going to work.    
   }
   ..
}

示例1:在泛型类中使用复制构造函数。

工厂方法没有标准名称。

拥有可重用代码的接口非常好。

public class MyClass<T>{
    ..
    public void copyData(T data){
        T copy=data.clone();//Throws an exception if the input was not cloneable
    }
    ..
}

示例2:在泛型类中使用clone()。

我注意到克隆不是静态方法,但是不是仍然需要制作所有受保护字段的深层副本吗?在实现clone()时,在非可克隆子类中抛出异常的额外工作对我来说似乎微不足道。

我错过了什么吗?任何见解都将不胜感激。

10 个答案:

答案 0 :(得分:49)

基本上,clone is broken。什么都不会轻易与泛型一起使用。如果你有这样的东西(缩短以获得重点):

public class SomeClass<T extends Copyable> {


    public T copy(T object) {
        return (T) object.copy();
    }
}

interface Copyable {
    Copyable copy();
}

然后使用编译器警告您可以完成工作。因为泛型在运行时被擦除,所以执行复制的东西会在其中生成编译器警告生成强制转换。 在这种情况下无法避免。。在某些情况下这是可以避免的(谢谢,kb304),但不是全部。考虑必须支持子类或实现接口的未知类的情况(例如,您正在迭代不一定生成相同类的可复制副本集合。)

答案 1 :(得分:25)

还有Builder模式。有关详细信息,请参阅Effective Java。

我不明白你的评价。在复制构造函数中,您完全了解类型,为什么需要使用泛型?

public class C {
   public int value;
   public C() { }
   public C(C other) {
     value = other.value;
   }
}

最近有一个类似的问题here

public class G<T> {
   public T value;
   public G() { }
   public G(G<? extends T> other) {
     value = other.value;
   }
}

可运行的样本:

public class GenTest {
    public interface Copyable<T> {
        T copy();
    }
    public static <T extends Copyable<T>> T copy(T object) {
        return object.copy();
    }
    public static class G<T> implements Copyable<G<T>> {
        public T value;
        public G() {
        }
        public G(G<? extends T> other) {
            value = other.value;
        }
        @Override
        public G<T> copy() {
            return new G<T>(this);
        }
    }
    public static void main(String[] args) {
        G<Integer> g = new G<Integer>();
        g.value = 1;
        G<Integer> f = g.copy();
        g.value = 2;
        G<Integer> h = copy(g);
        g.value = 3;
        System.out.printf("f: %s%n", f.value);
        System.out.printf("g: %s%n", g.value);
        System.out.printf("h: %s%n", h.value);
    }
}

答案 2 :(得分:8)

Java没有与C ++相同的复制构造函数。

你可以有一个构造函数,它接受一个与参数相同类型的对象,但很少有类支持这个。 (少于支持克隆的数量)

对于通用克隆,我有一个辅助方法,它创建一个类的新实例,并使用反射复制原始字段(浅拷贝)(实际上像反射但更快)

对于深层复制,一种简单的方法是序列化对象并对其进行反序列化。

BTW:我的建议是使用不可变对象,然后你就不需要克隆它们了。 ;)

答案 3 :(得分:5)

我认为Yishai的答案可以改进,所以我们不能使用以下代码发出警告:

public class SomeClass<T extends Copyable<T>> {

    public T copy(T object) {
        return object.copy();
    }
}

interface Copyable<T> {
    T copy();
}

这样一个需要实现Copyable接口的类必须是这样的:

public class MyClass implements Copyable<MyClass> {

    @Override
    public MyClass copy() {
        // copy implementation
        ...
    }

}

答案 4 :(得分:2)

以下是许多开发人员不使用Object.clone()

的缺点
  1. 使用Object.clone()方法要求我们为代码添加大量语法,例如实现Cloneable接口,定义clone()方法和处理CloneNotSupportedException,最后调用{{1并将它转换为我们的对象。
  2. Object.clone()接口缺少Cloneable方法,它是一个标记接口,并且没有任何方法,我们仍然需要实现它只是为了告诉JVM我们可以执行{{1我们的对象。
  3. clone()受到保护,因此我们必须提供自己的clone()并间接致电Object.clone()
  4. 我们对对象构造没有任何控制权,因为clone()不会调用任何构造函数。
  5. 如果我们在子类中编写Object.clone()方法,例如Object.clone()然后它的所有超类应该在其中定义clone()方法,或者从另一个父类继承它,否则Person链将会失败。
  6. clone()仅支持浅拷贝,因此我们新克隆对象的引用字段仍将保存原始对象的哪些字段所持有的对象。为了克服这个问题,我们需要在我们的类所持有的每个类中实现super.clone(),然后在我们的Object.clone()方法中单独克隆它们,如下例所示。
  7. 我们无法操纵clone()中的最终字段,因为最终字段只能通过构造函数进行更改。在我们的示例中,如果我们希望每个clone()对象都是id唯一的,那么我们将使用Object.clone()获取重复对象,因为Person不会调用构造函数而最终{{1无法从Object.clone()修改字段。
  8. 复制构造函数优于Object.clone(),因为它们

    1. 不要强迫我们实现任何接口或抛出任何异常,但如果需要,我们肯定可以这样做。
    2. 不要求任何演员。
    3. 不要求我们依赖于未知的对象创建机制。
    4. 不要求父母级遵守任何合同或实施任何合同。
    5. 允许我们修改最终字段。
    6. 让我们完全控制对象创建,我们可以在其中编写初始化逻辑。
    7. 详细了解Java Cloning - Copy Constructor versus Cloning

答案 5 :(得分:0)

可能适合您的一种模式是bean级复制。基本上,您使用无参数构造函数并调用各种setter来提供数据。您甚至可以使用各种bean属性库来相对轻松地设置属性。这与做一个clone()不同,但是出于许多实际目的,这很好。

答案 6 :(得分:0)

通常,clone()与受保护的复制构造函数协同工作。这样做是因为与构造函数不同,clone()可以是虚拟的。

在一个来自超类Base的Derived的类体中,我们有

class Derived extends Base {
}

因此,最简单的是,您将使用clone()添加一个虚拟副本构造函数。 (在C ++中,Joshi建议将clone作为虚拟拷贝构造函数。)

protected Derived() {
    super();
}

protected Object clone() throws CloneNotSupportedException {
    return new Derived();
}

如果您想按照建议调用super.clone()并且您必须将这些成员添加到类中,那么它会变得更复杂,您可以尝试这个

final String name;
Address address;

/// This protected copy constructor - only constructs the object from super-class and
/// sets the final in the object for the derived class.
protected Derived(Base base, String name) {
   super(base);
   this.name = name;
}

protected Object clone() throws CloneNotSupportedException {
    Derived that = new Derived(super.clone(), this.name);
    that.address = (Address) this.address.clone();
}

现在,如果执行,你有

Base base = (Base) new Derived("name");

然后你做了

Base clone = (Base) base.clone();

这将在Derived类(上面的那个)中调用clone(),这将调用super.clone() - 可能会也可能不会实现,但建议您调用它。然后,实现将super.clone()的输出传递给受保护的复制构造函数,该构造函数接受Base并将任何最终成员传递给它。

然后该拷贝构造函数调用超类的拷贝构造函数(如果你知道它有一个),并设置决赛。

当你回到clone()方法时,你设置任何非最终成员。

敏锐的读者会注意到,如果你在Base中有一个拷贝构造函数,它将由super.clone()调用 - 并且当你在受保护的构造函数中调用超级构造函数时将再次调用它,所以你可以两次调用超级复制构造函数。希望如果它锁定资源,它就会知道。

答案 7 :(得分:0)

Cloneable接口被打破了,因为它没用,但是克隆效果很好,可以为大对象带来更好的性能 - 8个字段甚至更多,但它会失败逃逸分析。所以最好在大多数时候使用复制构造函数。 在阵列上使用clone比Arrays.copyOf更快,因为长度保证是相同的。

此处有更多详情https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html

答案 8 :(得分:0)

如果一个人不是100%意识到clone()的所有怪癖,那么我建议远离它。我不会说clone()被打破了。我会说:只有当你完全确定它是你最好的选择时才使用它。 复制构造函数(或工厂方法,我认为并不重要)很容易编写(可能很冗长,但很简单),它只复制您想要复制的内容,并复制您希望复制内容的方式。您可以根据您的具体需求进行修剪。

另外:调用复制构造函数/工厂方法时,很容易调试会发生什么。

并且clone()并不会立即创建对象的“深层”副本,假设您的意思是不仅会复制引用(例如Collection)。但在这里阅读更多有关深度和浅度的信息 Deep copy, shallow copy, clone

答案 9 :(得分:-2)

您缺少的是,默认情况下,克隆会创建浅层副本,并且通常会使其创建深层副本,这是不可行的。

问题在于,您无法真正创建循环对象图的深层副本,而无法跟踪已访问过的对象。 clone()不提供这样的跟踪(因为它必须是.clone()的参数),因此只创建浅拷贝。

即使您自己的对象为其所有成员调用.clone,它仍然不是深层副本。