ccleve
ccleve

Reputation: 15779

Cloning subclasses in Java

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

Upvotes: 6

Views: 12208

Answers (7)

Steven Spungin
Steven Spungin

Reputation: 29081

This pattern/solution uses a combination of copy constructors and overridden typed virtual functions, and propagates the instance to each superclass.

public class A {
    private String myA;

    public A(A a) {
        myA = a.myA;
    }

    public A copy() {
        return new A(this);
    }
}

public class B extends A {
    private String myB;

    public B(B b) {
        super(b);
        myB = b.myB;
    }

    public B copy() {
        return new B(this);
    }
}

public class C extends B {
    private String myC;

    public C(C c) {
        super(c);
        this.myC = c.myC;
    }

    public C copy() {
        return new C(this);
    }
}

Upvotes: 0

gpeche
gpeche

Reputation: 22504

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.

Upvotes: 5

Ralph
Ralph

Reputation: 120771

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

Upvotes: 4

Ronnie Howell
Ronnie Howell

Reputation: 639

I'm not sure if this fits your requirements exactly, but I'd suggest taking a look at the Factory pattern.

Upvotes: -1

Reverend Gonzo
Reverend Gonzo

Reputation: 40811

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

Upvotes: 2

rsp
rsp

Reputation: 23373

The way to do this is to create a method newInstance() in Foo and Bar. Both implementations can create an object of the correct type and return that, the copying code only has to know that it must use oldFoo.newInstance() to get a copy of the object.

Upvotes: 0

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147154

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

Upvotes: 8

Related Questions