clone() vs copy constructor vs factory method?

2019-01-01 15:10发布

I did a quick google on implementing clone() in Java and found: http://www.javapractices.com/topic/TopicAction.do?Id=71

It has the following comment:

copy constructors and static factory methods provide an alternative to clone, and are much easier to implement.

All I want to do is make a deep copy. Implementing clone() seems to make a lot of sense, but this highly google ranked article makes me a bit afraid.

Here are the issues that I've noticed:

Copy constructors don't work with Generics.

Here's some pseudo-code that won't compile.

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

Sample 1: Using a copy constructor in a generic class.

Factory methods don't have standard names.

It's quite nice to have an interface for reusable code.

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

Sample 2: Using clone() in a generic class.

I noticed that clone is not a static method, but wouldn't it still be necessary to make deep copies of all the protected fields? When implementing clone(), the extra effort to throw exceptions in non-cloneable subclasses seems trivial to me.

Am I missing something? Any insights would be appreciated.

标签: java clone
10条回答
余生请多指教
2楼-- · 2019-01-01 15:17

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.

查看更多
刘海飞了
3楼-- · 2019-01-01 15:25

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).

查看更多
临风纵饮
4楼-- · 2019-01-01 15:25

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

查看更多
旧时光的记忆
5楼-- · 2019-01-01 15:26

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.

查看更多
浅入江南
6楼-- · 2019-01-01 15:27

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
        ...
    }

}
查看更多
看淡一切
7楼-- · 2019-01-01 15:28

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.

查看更多
登录 后发表回答