84

我对在 Java 中实现 clone() 做了一个快速的谷歌搜索,发现: http ://www.javapractices.com/topic/TopicAction.do?Id=71

它有以下评论:

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

我想做的就是做一个深拷贝。实现 clone() 似乎很有意义,但是这篇 google 排名很高的文章让我有点害怕。

以下是我注意到的问题:

复制构造函数不适用于泛型。

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

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 不是静态方法,但是是否仍然需要对所有受保护的字段进行深拷贝?在实现 clone() 时,在不可克隆子类中引发异常的额外努力对我来说似乎微不足道。

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

4

10 回答 10

53

基本上,克隆坏了。没有什么可以轻松地使用泛型。如果你有这样的事情(缩短以理解这一点):

public class SomeClass<T extends Copyable> {


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

interface Copyable {
    Copyable copy();
}

然后通过编译器警告,您可以完成工作。因为泛型在运行时被删除,所以复制的东西会在其中产生编译器警告。在这种情况下是无法避免的。. 在某些情况下是可以避免的(感谢 kb304),但并非完全如此。考虑您必须支持实现接口的子类或未知类的情况(例如,您正在遍历不一定生成相同类的可复制对象的集合)。

于 2009-07-09T20:03:05.630 回答
25

还有建造者模式。有关详细信息,请参阅有效的 Java。

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

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

最近这里有一个类似的问题。

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);
    }
}
于 2009-07-09T20:02:45.320 回答
11

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

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

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

对于深拷贝,一个简单的方法是序列化对象并反序列化它。

顺便说一句:我的建议是使用不可变对象,那么您就不需要克隆它们。;)

于 2009-07-09T19:59:53.670 回答
6

我认为 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
        ...
    }

}
于 2013-04-16T07:24:00.257 回答
6

以下是许多开发人员不使用的一些缺点Object.clone()

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

复制构造函数比Object.clone()因为它们更好

  1. 不要强迫我们实现任何接口或抛出任何异常,但如果需要,我们肯定可以做到。
  2. 不需要任何演员表。
  3. 不要求我们依赖未知的对象创建机制。
  4. 不要要求父类遵循任何合同或实施任何东西。
  5. 允许我们修改最终字段。
  6. 允许我们完全控制对象的创建,我们可以在其中编写初始化逻辑。

阅读有关Java 克隆的更多信息 - 复制构造函数与克隆

于 2017-01-11T18:09:47.113 回答
1

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

在 Derived from a super class Base 的类体中,我们有

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();

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

然后,该复制构造函数调用超类的复制构造函数(如果您知道它有一个),并设置 final。

当您回到 clone() 方法时,您设置了所有非最终成员。

精明的读者会注意到,如果您在 Base 中有一个复制构造函数,它将由 super.clone() 调用 - 当您在受保护的构造函数中调用超级构造函数时会再次调用它,因此您可能正在调用超级复制构造函数两次。希望如果它正在锁定资源,它会知道这一点。

于 2015-11-02T11:16:14.093 回答
0

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

于 2009-07-09T20:13:12.740 回答
0

Cloneable 接口已损坏,从某种意义上说,它无用,但克隆效果很好,并且可以为大对象带来更好的性能 - 8 个字段或更多,但它会导致逃逸分析失败。所以大多数时候最好使用复制构造函数。在数组上使用克隆比 Arrays.copyOf 更快,因为长度保证是相同的。

更多细节在这里https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html

于 2017-07-19T14:41:49.793 回答
0

如果一个人不是 100% 了解 的所有怪癖clone(),那么我会建议远离它。我不会说clone()破碎。我想说:只有在您完全确定它是您的最佳选择时才使用它。复制构造函数(或工厂方法,我认为这并不重要)很容易编写(可能很长,但很容易),它只复制您想要复制的内容,并以您想要复制的方式复制。您可以根据您的确切需求对其进行修剪。

另外:很容易调试调用复制构造函数/工厂方法时发生的情况。

并且clone()不会创建开箱即用的对象的“深层”副本,假设您的意思是不仅Collection复制了引用(例如对 a )。但是在这里阅读更多关于深和浅的信息: 深拷贝、浅拷贝、克隆

于 2017-09-13T13:44:02.933 回答
-2

您缺少的是克隆默认和约定创建浅拷贝,而使其创建深拷贝通常是不可行的。

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

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

于 2009-07-09T20:09:03.183 回答