如何正确覆盖克隆方法?

时间:2010-02-24 14:41:23

标签: java clone cloning cloneable

我需要在我的一个没有超类的对象中实现深度克隆。

处理超类(CloneNotSupportedException)抛出的已检查Object的最佳方法是什么?

一位同事建议我按照以下方式处理:

@Override
public MyObject clone()
{
    MyObject foo;
    try
    {
        foo = (MyObject) super.clone();
    }
    catch (CloneNotSupportedException e)
    {
        throw new Error();
    }

    // Deep clone member fields here

    return foo;
}

这对我来说似乎是一个很好的解决方案,但我想把它扔给StackOverflow社区,看看我是否还有其他任何见解。谢谢!

9 个答案:

答案 0 :(得分:118)

你绝对必须使用clone吗?大多数人都同意Java clone已被破坏。

Josh Bloch on Design - Copy Constructor versus Cloning

  

如果您已经阅读了我的书中有关克隆的内容,特别是如果您在这些内容之间进行了阅读,您会发现我认为clone已经被彻底打破了。 [...] Cloneable被打破是一种耻辱,但它确实发生了。

您可以在他的书 Effective Java 2nd Edition,Item 11:明智地覆盖clone 中阅读有关该主题的更多讨论。他建议改为使用复制构造函数或复制工厂。

他接着写了一些页面,说明如果你认为必须如何,你应该实施clone。但他以此结束了:

  

所有这些复杂性真的有必要吗?很少。如果扩展实现Cloneable的类,除了实现行为良好的clone方法之外别无选择。否则,您最好提供替代的对象复制方法,或者根本不提供该功能

重点是他,而不是我的。


由于您明确表示除了实施clone之外别无选择,所以在这种情况下您可以采取以下措施:确保MyObject extends java.lang.Object implements java.lang.Cloneable。如果是这种情况,那么您可以保证永远不会抓住CloneNotSupportedException。像某些人建议的那样抛出AssertionError似乎是合理的,但你也可以添加一个注释来解释为什么在这种特殊情况下永远不会输入catch块


或者,正如其他人也建议的那样,您可以在不调用clone的情况下实施super.clone

答案 1 :(得分:55)

有时候实现复制构造函数更简单:

public MyObject (MyObject toClone) {
}

它为您节省了处理CloneNotSupportedException的麻烦,适用于final字段,您无需担心要返回的类型。

答案 2 :(得分:11)

您的代码的工作方式非常接近于编写它的“规范”方式。不过,我会在捕获物中抛出AssertionError。它表示永远不应该到达那条线。

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}

答案 3 :(得分:9)

有两种情况会引发CloneNotSupportedException

  1. 克隆的类未实现Cloneable(假设实际克隆最终遵循Object的克隆方法)。如果您在实现Cloneable中编写此方法的类,则永远不会发生这种情况(因为任何子类都将适当地继承它)。
  2. 实现显式抛出异常 - 当超类为Cloneable时,这是防止子类中克隆性的推荐方法。
  3. 后一种情况不会发生在你的类中(因为你直接调用try块中的超类'方法,即使是从调用super.clone()的子类调用的),前者也不应该你的班级应该明确地实施Cloneable

    基本上,您应该确定记录错误,但在这个特定的实例中,只有当您搞砸了类的定义时才会发生错误。因此将其视为NullPointerException(或类似)的已检查版本 - 如果您的代码正常运行,它将永远不会被抛出。


    在其他情况下,您需要为此可能性做好准备 - 无法保证给定对象 可克隆,因此在捕获异常时,您应根据此条件采取适当的操作(继续使用现有对象,采用替代克隆策略,例如序列化 - 反序列化,如果您的方法需要可克隆的参数等,则抛出IllegalParameterException等。)

    编辑:虽然总的来说我应该指出是的,clone()确实很难正确实现,并且难以让来电者知道返回值是否是他们想要的,加倍当你考虑深克隆和浅克隆时。通常最好完全避免整个事情并使用其他机制。

答案 4 :(得分:5)

使用serialization制作深层副本。这不是最快的解决方案,但它不依赖于类型。

答案 5 :(得分:3)

您可以实现受保护的副本构造函数,如下所示:

/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
    this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
    this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
    // etc
}

public MyObject clone() {
    return new MyObject(this);
}

答案 6 :(得分:0)

public class MyObject implements Cloneable, Serializable{   

    @Override
    @SuppressWarnings(value = "unchecked")
    protected MyObject clone(){
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            ByteArrayOutputStream bOs = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bOs);
            oos.writeObject(this);
            ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
            return  (MyObject)ois.readObject();

        } catch (Exception e) {
            //Some seriouse error :< //
            return null;
        }finally {
            if (oos != null)
                try {
                    oos.close();
                } catch (IOException e) {

                }
            if (ois != null)
                try {
                    ois.close();
                } catch (IOException e) {

                }
        }
    }
}

答案 7 :(得分:0)

尽管这里的大部分答案都是有效的,但我需要告诉您,您的解决方案也是实际Java API开发人员的工作方式。 (Josh Bloch或Neal Gafter)

以下是openJDK,ArrayList类的摘录:

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

正如您已经注意到并且其他人提到的那样,如果您宣布实施CloneNotSupportedException接口,则Cloneable几乎没有机会被抛出。

此外,如果您没有在重写方法中执行任何新操作,则无需覆盖该方法。只需要在对象上执行额外操作或者需要将其公开时,您只需要覆盖它。

最终,最好还是避免使用其他方式。

答案 8 :(得分:0)

仅仅因为Java的Cloneable实现被破坏了,这并不意味着你不能创建自己的。

如果OP的真正目的是创建深度克隆,我认为可以创建这样的接口:

public interface Cloneable<T> {
    public T getClone();
}

然后使用前面提到的原型构造函数来实现它:

public class AClass implements Cloneable<AClass> {
    private int value;
    public AClass(int value) {
        this.vaue = value;
    }

    protected AClass(AClass p) {
        this(p.getValue());
    }

    public int getValue() {
        return value;
    }

    public AClass getClone() {
         return new AClass(this);
    }
}

和另一个带有AClass对象字段的类:

public class BClass implements Cloneable<BClass> {
    private int value;
    private AClass a;

    public BClass(int value, AClass a) {
         this.value = value;
         this.a = a;
    }

    protected BClass(BClass p) {
        this(p.getValue(), p.getA().getClone());
    }

    public int getValue() {
        return value;
    }

    public AClass getA() {
        return a;
    }

    public BClass getClone() {
         return new BClass(this);
    }
}

通过这种方式,您可以轻松深度克隆类BClass的对象,而无需@SuppressWarnings或其他花哨的代码。