如何正确覆盖克隆方法?


114

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

处理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社区,以查看是否有我可以提供的其他见解。谢谢!


6
如果您知道父类实现了,Cloneable那么抛出,AssertionError而不是仅仅抛出一个普通的Error表达。
安德鲁·达菲

编辑:我宁愿不使用clone(),但该项目已经基于它了,此时不值得重构所有引用。
Cuga

最好现在就硬着头皮,而不是以后再咬(好吧,除非您即将投入生产)。
Tom Hawtin-大头钉

日程安排还剩下一个半月,直到完成。我们无法证明时间合理。
Cuga 2010年

我认为这个解决方案是完美的。而且不要为使用克隆而感到难过。如果您有一个用作传输对象的类,并且它仅包含primitves和不可变对象(例如String和Enums),则出于教条的原因,除clone之外的所有其他内容都将完全浪费时间。只要记住克隆做什么,什么不做!(无深度克隆)
TomWolk 2015年

Answers:


125

您绝对必须使用clone吗?大多数人都认为Java clone是坏的。

Josh Bloch谈设计-复制构造函数与克隆

如果您已经阅读了我书中有关克隆的内容,尤其是您在两行之间阅读时,您会知道我认为它clone已被深深地打破了。[...]这是一种耻辱Cloneable,但确实发生了。

您可以在他的有效Java第二版,第11项:clone明智地改写中阅读有关该主题的更多讨论。他建议改为使用复制构造函数或复制工厂。

他接着写了几页关于如何,如果您认为必须执行的书clone。但是他结束了:

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

重点是他而不是我的。


既然您已经清楚地表明除了执行之外别无选择clone,因此在这种情况下,您可以执行以下操作:确保MyObject extends java.lang.Object implements java.lang.Cloneable。如果是这种情况,那么您可以保证永远不会抓到CloneNotSupportedExceptionAssertionError如某些人建议的那样进行抛出似乎是合理的,但是您也可以添加一条注释,以解释为什么在这种特殊情况下永远不会输入catch块。


另外,正如其他人所建议的那样,您可以clone不调用而实现super.clone


1
不幸的是,该项目已经使用clone方法编写,否则我绝对不会使用它。我完全同意您的观点,Java的克隆实现是fakakta。
卡加

5
如果一个类及其所有超类super.clone()在其克隆方法中调用,则子类通常仅clone()在其添加需要克隆其内容的新字段时才需要重写。如果有任何超类使用new而不是super.clone(),则所有子类都必须覆盖clone()它们是否添加任何新字段。
supercat 2012年

56

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

public MyObject (MyObject toClone) {
}

它为您节省了处理麻烦CloneNotSupportedException,可以使用final字段的麻烦,并且您不必担心要返回的类型。


11

您的代码的工作方式非常类似于“规范”的编写方式。不过,我会AssertionError在陷阱中扔掉。它表明该行永远都不会到达。

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

有关为什么这可能不是一个好主意,请参见@polygenelubricants的答案。
里希

@KarlRichter我已经阅读了他们的答案。并不是说这是一个坏主意,除了Cloneable有效的Java中所解释的那样,总的来说是一个坏主意。OP已经表示必须使用Cloneable。所以我不知道我的答案还有什么其他改进之处,除非完全删除它。
克里斯·杰斯特·杨

9

在两种情况下CloneNotSupportedException会抛出:

  1. 所克隆的类未实现Cloneable(假设实际的克隆最终遵循Object的clone方法)。如果您在Implements中编写此方法的类Cloneable将永远不会发生(因为任何子类都会适当地继承它)。
  2. 异常是由实现显式抛出的-当超类为时,这是防止子类中的可克隆性的推荐方法Cloneable

后一种情况不会在您的类中发生(因为您直接在try块中调用了超类的方法,即使从子类调用中调用了该方法super.clone()),前一种情况也不会发生,因为您的类显然应该实现Cloneable

基本上,您应该确定会记录该错误,但是在这种特定情况下,只有在弄乱了类的定义时才会发生。因此,将其视为NullPointerException(或相似)的检查版本-如果您的代码正常运行,则永远不会抛出该错误。


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

编辑:尽管总的来说,我应该指出,是的,clone()确实很难正确实现,并且调用者很难知道返回值是否是他们想要的值,所以在考虑深克隆还是浅克隆时,这是双重的。通常最好是完全避免整个事情并使用另一种机制。


如果某个对象公开了公共克隆方法,则任何不支持该方法的派生对象都将违反Liskov替换原则。如果克隆方法受到保护,我认为最好用除返回正确类型的方法以外的其他方法来对其进行阴影处理,以防止子类甚至尝试调用super.clone()
超级猫


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

3
在大多数情况下,这是行不通的。除非它具有方法clone()getMySecondMember()否则无法调用返回的对象public clone
polygenelubricants

显然,您需要在要克隆的每个对象上实现此模式,或者找到另一种方法来深度克隆每个成员。
Dolph

是的,这是必要条件。
polygenelubricants

1

尽管这里的大多数答案都是有效的,但我需要告诉您的解决方案也是实际的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接口则几乎没有机会被抛出。

也,如果您在覆盖的方法中未做任何新操作,则无需覆盖该方法。仅在需要对对象执行其他操作或将其公开时,才需要覆盖它。

最终,仍然最好避免使用其他方式来做到这一点。


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

                }
        }
    }
}

因此,您刚刚将其写入文件系统并已读回该对象。好的,这是处理克隆的最佳方法吗?SO社区的任何人都可以对此方法发表评论吗?我猜这不必要地将克隆和序列化联系在一起-两个完全不同的概念。我将拭目以待别人对此有何评论。
Saurabh Patil 2014年

2
它不在文件系统中,而是在主内存(ByteArrayOutputStream)中。对于深度嵌套的对象,其解决方案效果很好。特别是如果您不经常使用它,例如在单元测试中。性能不是主要目标的地方。
AlexWien

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或其他other头的代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.