Cloning subclasses in Java

10,396

Solution 1

If you have control of the classes you are trying to copy, then a virtual method is the way forward:

class Foo {
    ...
    public Foo copy() {
        return new Foo(this);
    }
}
class Bar extends Foo {
    ...
    @Override public Bar copy() {
        return new Bar(this);
    }
}

(Ideally make classes either abstract or effectively final.)

Solution 2

The most standard way in Java would be to make each class that may be cloned implement Cloneable, and override Object.clone() with a public version that does the cloning appropiately for that class (by default Object.clone() makes a shallow copy of the object).

Note that many people think that Cloneable/Object.clone() is bad design.

Solution 3

If you implement clone / clonable in the correct way you will not have any super class problem. This is because Object.clone() is a very special method - in a very briefe description: Object.clone() will always return a object of the same type it is invoked.

@see http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#clone%28%29

So the correct implementation of clone and clonable would be:

class Foo implements Clonable{
    String myFoo;
    ...
    Foo clone() {
      try {
        Foo clone = (Foo) super.clone();
        clone.myFoo = this.myFoo;
        return clone;
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException("this could never happen", e);
      }
    }
}

class Bar extends Foo {
    String myBar;
    ...
    Bar clone() {
      try {
        Bar clone = (Bar) super.clone();
        clone.myBar = this.myBar();
        return clone;
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException("this could never happen", e);
      }
    }
}

Then the implementation of Copier is easy:

class Copier {
  Foo foo;

  /**
   * @return return a Foo if oldFoo is of class Foo, return Bar if oldClass is of class Bar.
   */
  public Foo makeCopy(Foo oldFoo) {
    return oldFoo.clone();
  }
}

Solution 4

If you need to deep clone an object, the best way is to use Java serialization. This requires that object implements Serializable but it will create a completely new, cloned object with no shared references to the original.

You could have your primary class implement Serializable, thereby every subclass will automatically support it.

Lastly, this can probably be updated to use Piped Streams rather than ByteArrayOutputStream to use less memory and be faster though, it won't be noticeable if you have small objects.

public static<T> T clone(T object) {
    try {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bOut);
        out.writeObject(object);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
        T copy = (T)in.readObject();
        in.close();

        return copy;
    }
    catch(Exception e) {
        throw new RuntimeException(e);
    }
}
Share:
10,396
ccleve
Author by

ccleve

Updated on June 04, 2022

Comments

  • ccleve
    ccleve almost 2 years

    I need to clone a subclass in Java, but at the point in the code where this happens, I won't know the subclass type, only the super class. What's the best design pattern for doing this?

    Example:

    class Foo {
        String myFoo;
        public Foo(){}
        public Foo(Foo old) {
            this.myFoo = old.myFoo;
        }
    }
    
    class Bar extends Foo {
        String myBar;
        public Bar(){}
        public Bar(Bar old) {
            super(old); // copies myFoo
            this.myBar = old.myBar;
        }
    }
    
    class Copier {
        Foo foo;
    
        public Foo makeCopy(Foo oldFoo) {
    
            // this doesn't work if oldFoo is actually an
            // instance of Bar, because myBar is not copied
            Foo newFoo = new Foo(oldFoo);
            return newFoo;
    
            // unfortunately, I can't predict what oldFoo's the actual class
            // is, so I can't go:
            // if (oldFoo instanceof Bar) { // copy Bar here }
        }
    }
    
  • ccleve
    ccleve over 13 years
    Nope, doesn't work. Try this: Bar bar = new Bar(); Foo foo = bar; foo.copy(). It will invoke Foo.copy(), not Bar.copy()
  • Brett
    Brett over 13 years
    @user237815 I don't think so. Bar.copy overrides (checked with @Override) Foo.copy.
  • Steve Kuo
    Steve Kuo over 13 years
    Tom is correct. The invocation of copy happens at runtime and the JVM will call the appropriate copy method based on the object's type.
  • Lawrence Dol
    Lawrence Dol over 13 years
    To me the name newInstance() implies a new unrelated object, not a copy/clone.
  • rsp
    rsp over 13 years
    I kinda expected a reaction like this after I added the answer... The method name depends on the context/concepts you want to express (like copy, clone, getInstance, etc.) So any method name example used in the answer is probably not what the OP is going to use. To me the name newInstance signals a new object instance which in this case can be initialised with the values of the object executing the newInstance method. (As opposed to a static newInstance method.)
  • Hussein Akar
    Hussein Akar over 3 years
    Factory pattern used to decide which instance to get on runtime and the returned type must always of super class ... doesn't help in this case.