如何正确重写克隆方法?

发布于 2024-08-22 18:27:03 字数 493 浏览 17 评论 0原文

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

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

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

@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 社区,看看是否还有其他我可以包含的见解。谢谢!

I need to implement a deep clone in one of my objects which has no superclass.

What is the best way to handle the checked CloneNotSupportedException thrown by the superclass (which is Object)?

A coworker advised me to handle it the following way:

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

    // Deep clone member fields here

    return foo;
}

This seems like a good solution to me, but I wanted to throw it out to the StackOverflow community to see if there are any other insights I can include. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

弥繁 2024-08-29 18:27:03

您绝对必须使用克隆吗?大多数人都认为 Java 的克隆已被破坏。

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

如果你读过我书中有关克隆的内容,特别是如果你读到了字里行间的内容,你就会知道我认为克隆已经被彻底破坏了。 [...] 遗憾的是 Cloneable 被破坏了,但它确实发生了。

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

他接着写了一页又一页的内容来说明如果您认为必须的话,应该如何实现克隆。但他最后这样说:

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

重点是他的,不是我的。


既然您明确表示除了实现克隆之外别无选择,在这种情况下您可以执行以下操作:确保 MyObject 扩展 java.lang.Object 实现 java.lang.Cloneable 。如果是这种情况,那么您可以保证您永远不会捕获CloneNotSupportedException。正如一些人建议的那样抛出 AssertionError 似乎是合理的,但您也可以添加注释来解释为什么 在这种特殊情况下永远不会输入 catch 块。


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

Do you absolutely have to use clone? Most people agree that Java's clone is broken.

Josh Bloch on Design - Copy Constructor versus Cloning

If you've read the item about cloning in my book, especially if you read between the lines, you will know that I think clone is deeply broken. [...] It's a shame that Cloneable is broken, but it happens.

You may read more discussion on the topic in his book Effective Java 2nd Edition, Item 11: Override clone judiciously. He recommends instead to use a copy constructor or copy factory.

He went on to write pages of pages on how, if you feel you must, you should implement clone. But he closed with this:

Is all this complexities really necessary? Rarely. If you extend a class that implements Cloneable, you have little choice but to implement a well-behaved clone method. Otherwise, you are better off providing alternative means of object copying, or simply not providing the capability.

The emphasis was his, not mine.


Since you made it clear that you have little choice but to implement clone, here's what you can do in this case: make sure that MyObject extends java.lang.Object implements java.lang.Cloneable. If that's the case, then you can guarantee that you will NEVER catch a CloneNotSupportedException. Throwing AssertionError as some have suggested seems reasonable, but you can also add a comment that explains why the catch block will never be entered in this particular case.


Alternatively, as others have also suggested, you can perhaps implement clone without calling super.clone.

绿萝 2024-08-29 18:27:03

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

public MyObject (MyObject toClone) {
}

它为您省去了处理 CloneNotSupportedException 的麻烦,可与 Final 字段一起使用,并且您不必担心要返回的类型。

Sometimes it's more simple to implement a copy constructor:

public MyObject (MyObject toClone) {
}

It saves you the trouble of handling CloneNotSupportedException, works with final fields and you don't have to worry about the type to return.

谈下烟灰 2024-08-29 18:27:03

您的代码的工作方式非常接近“规范”的编写方式。不过,我会在 catch 中抛出一个 AssertionError 。它表明永远不应该到达该线。

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

The way your code works is pretty close to the "canonical" way to write it. I'd throw an AssertionError within the catch, though. It signals that that line should never be reached.

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}
柠檬 2024-08-29 18:27:03

有两种情况会抛出 CloneNotSupportedException

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

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

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


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

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

There are two cases in which the CloneNotSupportedException will be thrown:

  1. The class being cloned does not implemented Cloneable (assuming that the actual cloning eventually defers to Object's clone method). If the class you are writing this method in implements Cloneable, this will never happen (since any sub-classes will inherit it appropriately).
  2. The exception is explicitly thrown by an implementation - this is the recommended way to prevent clonability in a subclass when the superclass is Cloneable.

The latter case cannot occur in your class (as you're directly calling the superclass' method in the try block, even if invoked from a subclass calling super.clone()) and the former should not since your class clearly should implement Cloneable.

Basically, you should log the error for sure, but in this particular instance it will only happen if you mess up your class' definition. Thus treat it like a checked version of NullPointerException (or similar) - it will never be thrown if your code is functional.


In other situations you would need to be prepared for this eventuality - there is no guarantee that a given object is cloneable, so when catching the exception you should take appropriate action depending on this condition (continue with the existing object, take an alternative cloning strategy e.g. serialize-deserialize, throw an IllegalParameterException if your method requires the parameter by cloneable, etc. etc.).

Edit: Though overall I should point out that yes, clone() really is difficult to implement correctly and difficult for callers to know whether the return value will be what they want, doubly so when you consider deep vs shallow clones. It's often better just to avoid the whole thing entirely and use another mechanism.

罪歌 2024-08-29 18:27:03

使用序列化来制作深层复制。这不是最快的解决方案,但它不依赖于类型。

Use serialization to make deep copies. This is not the quickest solution but it does not depend on the type.

怀中猫帐中妖 2024-08-29 18:27:03

您可以像这样实现受保护的复制构造函数:

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

You can implement protected copy constructors like so:

/* 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);
}
带上头具痛哭 2024-08-29 18:27:03
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) {

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

                }
        }
    }
}
素染倾城色 2024-08-29 18:27:03

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

此外,如果您不在重写的方法中执行任何新操作,则无需重写该方法。仅当需要对对象执行额外操作或需要将其公开时才需要覆盖它。

最终,最好还是避免它并使用其他方式来实现。

As much as the most of the answers here are valid, I need to tell that your solution is also how the actual Java API developers do it. (Either Josh Bloch or Neal Gafter)

Here is an extract from openJDK, ArrayList class:

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

As you have noticed and others mentioned, CloneNotSupportedException has almost no chance to be thrown if you declared that you implement the Cloneable interface.

Also, there is no need for you to override the method if you don't do anything new in the overridden method. You only need to override it when you need to do extra operations on the object or you need to make it public.

Ultimately, it is still best to avoid it and do it using some other way.

为人所爱 2024-08-29 18:27:03

仅仅因为 Java 的 Cloneable 实现被破坏,并不意味着您不能创建自己的 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 或其他花哨的代码。

Just because java's implementation of Cloneable is broken it doesn't mean you can't create one of your own.

If OP real purpose was to create a deep clone, i think that it is possible to create an interface like this:

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

then use the prototype constructor mentioned before to implement it:

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

and another class with an AClass object field:

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

In this way you can easely deep clone an object of class BClass without need for @SuppressWarnings or other gimmicky code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文