在Java中克隆子类

6
我需要在Java中克隆一个子类,但在代码的这一点上,我不知道子类的类型,只知道超类。有什么最好的设计模式可以做到这一点吗?
示例:
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 }
    }
}
7个回答

7

如果您可以控制要复制的类,那么使用虚方法是前进的道路:

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

(理想情况下,将类定义为抽象类或有效地定义为最终类。)

不行,不起作用。试试这个:Bar bar = new Bar(); Foo foo = bar; foo.copy()。它会调用Foo.copy(),而不是Bar.copy()。 - ccleve
2
@user237815 我不这么认为。Bar.copy 重写了(使用 @Override 检查)Foo.copy - Tom Hawtin - tackline
汤姆是正确的。 copy 的调用发生在运行时,JVM将根据对象的类型调用适当的 copy 方法。 - Steve Kuo

5
在Java中最标准的方式是让每个可能被克隆的类实现 Cloneable 接口,并重写一个公共版本的 Object.clone() 方法,以适当地对该类进行克隆(默认情况下,Object.clone() 会创建对象的浅拷贝)。
请注意,很多人认为 Cloneable/Object.clone() 是一个糟糕的设计。

4
如果您正确实现了克隆/可克隆,就不会有任何超类问题。这是因为Object.clone()是一个非常特殊的方法 - 简要描述:Object.clone()总是返回一个与其调用类型相同的对象。
请参见http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#clone%28%29 因此,正确的克隆和可克隆实现应该是:
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);
      }
    }
}

然后,复制机的实现很容易:
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();
  }
}

2
如果您需要深度克隆一个对象,最好的方法是使用Java序列化。这要求对象实现Serializable接口,但它将创建一个全新的、克隆的对象,没有与原始对象共享的引用。
您可以让主类实现Serializable接口,这样每个子类都会自动支持它。
最后,这可能可以更新为使用Piped Streams而不是ByteArrayOutputStream来使用更少的内存并且更快,尽管如果您有小对象,这不会很明显。
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);
    }
}

0
做法是在FooBar中创建一个方法newInstance()。两个实现都可以创建正确类型的对象并返回,复制代码只需要知道它必须使用oldFoo.newInstance()来获取对象的副本。

对我来说,newInstance()这个名称意味着一个新的不相关的对象,而不是一个副本/克隆。 - Lawrence Dol
在我添加答案后,我有点预料到会有这样的反应...方法名称取决于您想要表达的上下文/概念(例如copy、clone、getInstance等)。因此,在答案中使用的任何方法名称示例可能不是OP将要使用的。对我来说,newInstance的名称表示一个新的对象实例,该实例可以使用执行newInstance方法的对象的值进行初始化。(与静态newInstance方法相反。) - rsp

0

这个模式/解决方案使用复制构造函数和重写类型化虚拟函数的组合,并将实例传播到每个超类。

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

-1

我不确定这是否完全符合您的要求,但我建议您看一下工厂模式


工厂模式用于决定在运行时获取哪个实例,返回的类型必须始终是超类...在这种情况下没有帮助。 - Hussein Akar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接