克隆() vs 复制建构子 vs 工厂方法?

我对在 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 ()时,在不可克隆的子类中抛出异常的额外工作对我来说似乎微不足道。

我是不是错过了什么? 如果你能提供点有用的信息,我会很感激的。

58541 次浏览

Java doesn't have copy constructors in the same sense that C++ does.

You can have a constructor which takes an object of the same type as an argument, but few classes support this. (less than the number which support clone able)

For a generic clone I have a helper method which creates a new instance of a class and copies the fields from the original (a shallow copy) using reflections (actually something like reflections but faster)

For a deep copy, a simple approach is to serialize the object and de-serialize it.

BTW: My suggest is to use immutable objects, then you won't need to clone them. ;)

There is also the Builder pattern. See Effective Java for details.

I don't understand your evaluation. In a copy constructor you are fully aware of the type, why is there a need to use generics?

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

There was a similar question recently here.

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

A runnable sample:

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

Basically, clone is broken. Nothing will work with generics easily. If you have something like this (shortened to get the point across):

public class SomeClass<T extends Copyable> {




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


interface Copyable {
Copyable copy();
}

Then with a compiler warning you can get the job done. Because generics are erased at runtime, something that does a copy is going to have a compiler warning generating cast in it. It is not avoidable in this case.. It is avoidable in some cases (thanks, kb304) but not in all. Consider the case where you have to support a subclass or an unknown class implementing the interface (such as you were iterating through a collection of copyables that didn't necessarily generate the same class).

What you are missing is that clone creates shallow copies by default and convention, and that making it create deep copies is, in general, not feasible.

The problem is that you cannot really create deep copies of cyclic object graphs, without being able to keep track what objects have been visited. clone() does not provide such tracking (as that would have to be a parameter to .clone()), and thus only creates shallow copies.

Even if your own object invokes .clone for all of its members, it still won't be a deep copy.

One pattern that may work for you is bean-level copying. Basically you use a no-arg constructor and call various setters to provide the data. You can even use the various bean property libraries to set the properties relatively easily. This isn't the same as doing a clone() but for many practical purposes it's fine.

I think Yishai answer could be improved so we can have no warning with the following code:

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


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


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

This way a class that need to implements Copyable interface has to be like this:

public class MyClass implements Copyable<MyClass> {


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


}

Usually, clone() works in tandem with a protected copy constructor. This is done because clone(), unlike a constructor, can be virtual.

In a class body for Derived from a super class Base we have

class Derived extends Base {
}

So, at its most simplistic, you would add to this a virtual copy constructor with the clone(). (In C++, Joshi recommends clone as the virtual copy constructor.)

protected Derived() {
super();
}


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

It gets more complicated if you want to call super.clone() as is recommended and you have to add these members to the class, you can try this

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

Now, if an execution, you got

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

and you then did

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

this would invoke, clone() in the Derived class (the one above), this would invoke super.clone() - which may or may not be implemented, but you're advised to call it. The implementation then passes output of super.clone() to a protected copy constructor that takes a Base and you pass any final members to it.

That copy constructor then invokes the copy constructor of the super-class (if you know that it has one), and sets the finals.

When you come back to the clone() method, you set any non-final members.

Astute readers will notice that if you have a copy-constructor in the Base, it will be called by super.clone() - and will be called again when you invoke the super-constructor in the protected constructor, so you may be calling the super copy-constructor twice. Hopefully, if it is locking resources, it will know that.

Below are some cons due to which many developers don't use Object.clone()

  1. Using Object.clone() method requires us to add lots of syntax to our code like implement Cloneable interface, define clone() method and handle CloneNotSupportedException and finally call to Object.clone() and cast it our object.
  2. Cloneable interface lacks clone() method, it is a marker interface and doesn’t have any method in it, and still we need to implement it just to tell JVM that we can perform clone() on our object.
  3. Object.clone() is protected so we have to provide our own clone() and indirectly call Object.clone() from it.
  4. We don’t have any control over object construction because Object.clone() doesn’t invoke any constructor.
  5. If we are writing clone() method in a child class e.g. Person then all of its superclasses should define clone() method in them or inherit it from another parent class otherwise super.clone() chain will fail.
  6. Object.clone() support only shallow copy, so reference fields of our newly cloned object will still hold objects which fields of our original object was holding. In order to overcome this, we need to implement clone() in every class who’s reference our class is holding and then clone them separately in our clone() method like in below example.
  7. We can not manipulate final fields in Object.clone() because final fields can only be changed through constructors. In our case, if we want every Person object to be unique by id, we will get the duplicate object if we use Object.clone() because Object.clone() will not call the constructor and final id field can’t be modified from Person.clone().

Copy constructors are better than Object.clone() because they

  1. Don’t force us to implement any interface or throw any exception but we can surely do it if it is required.
  2. Don’t require any casts.
  3. Don’t require us to depend on an unknown object creation mechanism.
  4. Don’t require parent class to follow any contract or implement anything.
  5. Allow us modify final fields.
  6. Allow us to have complete control over object creation, we can write our initialization logic in it.

Read more on Java Cloning - Copy Constructor versus Cloning

The Cloneable interface is broken, in the sense that it is useless but clone works well and can lead to better performance for big objects - 8 fields and more, but it will then fail the escape analysis. so preferable to use copy constructor most of the time. Using clone on array is faster than Arrays.copyOf because the length is guaranteed to be the same.

more details here https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html

If one is not 100% aware of all the quirks of clone(), then I would advise to stay away from it. I would not say that clone() broken. I would say: use it only when you are entirely sure it is your best option. A copy constructor (or factory method, that doesn't really matter I think) is easy to write (maybe lengthy, but easy), it only copies what you want to be copied, and copies the way you want things to be copied. You can trim it to your exact needs.

Plus: it's easy to debug what happens when you call your copy constructor / factory method.

And clone() does not create a "deep" copy of your object out of the box, assuming you mean that not only the references (e.g. to a Collection) are copied over. But read more about deep and shallow here: Deep copy, shallow copy, clone