克隆()与复制构造函数与工厂方法?
我做了一个快速的谷歌在Java中实现克隆(),发现: http : //www.javapractices.com/topic/TopicAction.do?Id=71
它有以下评论:
复制构造函数和静态工厂方法提供了克隆的替代方法,并且更容易实现。
我只想做一个深层的复制。 实现克隆()似乎很有意义,但这个高度谷歌排名的文章使我有点害怕。
以下是我注意到的问题:
复制构造函数不适用于generics。
这是一些不能编译的伪代码。
public class MyClass<T>{ .. public void copyData(T data){ T copy=new T(data);//This isn't going to work. } .. }
示例1:在generics类中使用复制构造函数。
工厂方法没有标准名称。
有一个可重用代码的接口是相当不错的。
public class MyClass<T>{ .. public void copyData(T data){ T copy=data.clone();//Throws an exception if the input was not cloneable } .. }
示例2:在generics类中使用clone()。
我注意到克隆不是一个静态的方法,但是是否有必要对所有受保护的字段进行深度拷贝? 在实现clone()时,在非可复制子类中引发exception的额外工作对我来说似乎是微不足道的。
我错过了什么吗? 任何见解,将不胜感激。
基本上, 克隆被打破 。 没有什么可以轻松使用generics。 如果你有这样的事情(缩短以获得点):
public class SomeClass<T extends Copyable> { public T copy(T object) { return (T) object.copy(); } } interface Copyable { Copyable copy(); }
然后用编译器警告你可以完成工作。 由于generics在运行时被擦除,所以执行副本的东西将会有一个编译器警告,在其中生成投射。 在这种情况下是不可避免的。 。 这在某些情况下是可以避免的(谢谢,kb304),但不是全部。 考虑一下你必须支持一个实现接口的子类或者一个未知的类(比如你正在遍历一个不需要生成相同类的可拷贝集合)。
还有Builder模式。 有关详细信息,请参阅Effective Java
我不明白你的评价。 在复制构造函数中,您完全知道types,为什么需要使用generics?
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); } }
Java并没有像C ++那样的拷贝构造函数。
你可以有一个构造函数,它接受一个与参数types相同的对象,但是很less有类支持这个。 (less于支持克隆的数量)
对于一个通用的克隆,我有一个辅助方法,它创build一个类的新实例,并使用reflection从原始(浅拷贝)复制字段(实际上类似reflection,但更快)
对于深层复制,一个简单的方法是序列化对象并对其进行反序列化。
顺便说一句:我的build议是使用不可变的对象,那么你将不需要克隆它们。 ;)
我觉得易赛答案可以改进,所以我们可以不用警告下面的代码:
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 ... } }
下面是一些由于许多开发人员不使用Object.clone()
- 使用Object.clone()方法需要我们为代码添加很多语法,比如实现Cloneable接口,定义clone()方法和处理CloneNotSupportedException,最后调用Object.clone()并将其转换为我们的对象。
- Cloneable接口缺lessclone()方法,实际上Cloneable是一个标记接口,并没有任何方法,只是为了告诉JVM我们可以在我们的对象上执行clone()。
- Object.clone()是受保护的,所以我们必须提供自己的clone()并间接调用Object.clone()。
- 我们没有对对象构造的任何控制,因为Object.clone()不会调用任何构造函数。
- 如果我们在子类(例如Person)中编写克隆方法,则其所有超类应该在其中定义clone()方法,或者从另一个父类inheritance它,否则super.clone()链将失败。
- Object.clone()仅支持浅拷贝,因此我们新克隆的对象的引用字段仍然会保存对象的原始对象的哪个字段。 为了克服这个问题,我们需要在我们的类所引用的每个类中实现clone(),然后在clone()方法中分别调用它们,如下例所示。
- 我们不能在Object.clone()中操作final字段,因为final字段只能通过构造函数来改变。 在我们的例子中,如果我们希望每个Person对象通过id是唯一的,那么如果我们使用Object.clone(),我们会得到重复对象,因为Object.clone()不会调用构造函数,final final id字段不能被修改来自Person.clone()。
复制构造函数比Object.clone()更好,因为它们
- 不要强迫我们实现任何接口或抛出任何exception,但是如果需要的话,我们一定可以做到。
- 不要求任何演员。
- 不要求我们依赖一个未知的对象创build机制。
- 不要求父母遵守任何合同或执行任何事情。
- 允许我们修改最终字段。
- 让我们完全控制对象创build,我们可以在其中编写我们的初始化逻辑。
阅读Java克隆的更多信息- 复制构造函数与克隆
一个可能适合你的模式是bean级的复制。 基本上你使用无参数的构造函数,并调用各种设置器来提供数据。 甚至可以使用各种bean属性库来相对容易地设置属性。 这与做一个clone()不一样,但是对于很多实际的目的来说,这很好。
通常,clone()与受保护的拷贝构造函数一起工作。 这样做是因为与构造函数不同,clone()可以是虚拟的。
在一个超类基础派生的类体中
class Derived extends Base { }
因此,在最简单的情况下,您可以使用clone()添加一个虚拟拷贝构造函数。 (在C ++中,Joshibuild议克隆为虚拟拷贝构造函数。)
protected Derived() { super(); } protected Object clone() throws CloneNotSupportedException { return new Derived(); }
如果你想按照build议调用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()在Derived类(上面的一个),这将调用super.clone() – 可能或可能不会实现,但build议您调用它。 然后,实现将super.clone()的输出传递给受保护的拷贝构造函数,该构造函数接受一个Base并将任何最终成员传递给它。
该复制构造函数然后调用超级类的复制构造函数(如果您知道它有一个),并设置决赛。
当你回到clone()方法时,你设置了任何非final的成员。
精明的读者会注意到,如果在Base中有一个复制构造函数,它将被super.clone()调用 – 并且在调用受保护的构造函数中的超级构造函数时将被再次调用,因此您可能正在调用超级拷贝构造函数两次。 希望如果是locking资源,它会知道的。
Cloneable接口被破坏了,因为它是无用的,但是克隆效果很好,并且可以导致大对象(8个字段或更多)的更好性能,但是这样会导致逃逸分析失败。 所以大多数情况下最好使用复制构造函数。 使用数组上的克隆比Arrays.copyOf快,因为长度保证是相同的。
更多细节在这里https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html
如果不是100%意识到clone()
的所有怪癖,那么我build议远离它。 我不会说clone()
破碎。 我会说:只有当你完全确定这是你最好的select时才使用它。 复制构造函数(或工厂方法,我认为并不重要)很容易编写(也许冗长,但容易),它只复制你想要复制的内容,并复制你想要的东西复制的方式。 你可以修剪它,以确切的需要。
另外:调用复制构造函数/工厂方法时,debugging会发生什么很容易。
而clone()
不会创build对象的“深层”副本,假设你的意思是不仅仅是引用(例如一个Collection
)被拷贝过来。 但是在这里深入浅谈: 深层复制,浅层复制,克隆
你所缺less的是,克隆默认和惯例创build浅拷贝,并且创build深拷贝通常是不可行的。
问题是你不能真正创build循环对象图的深层副本,而不能跟踪什么对象被访问过。 clone()不提供这样的跟踪(因为它必须是.clone())的参数,因此只能创build浅拷贝。
即使您自己的对象为其所有成员调用.clone,它仍然不会是深层副本。