【问题标题】:How to properly override clone method?如何正确覆盖克隆方法?
【发布时间】:2011-01-20 13:41:14
【问题描述】:

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

处理超类(即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 社区,看看是否还有其他可以包含的见解。谢谢!

【问题讨论】:

  • 如果你知道父类实现了Cloneable,那么抛出一个AssertionError 而不仅仅是一个普通的Error 更有表现力。
  • 编辑:我宁愿不使用 clone(),但项目已经基于它,此时不值得重构所有引用。
  • 最好现在咬紧牙关而不是以后(好吧,除非您即将投入生产)。
  • 我们距离完成还有一个半月的时间。我们无法证明时间的合理性。
  • 我认为这个解决方案是完美的。并且不要对使用克隆感到难过。如果您有一个用作传输对象的类,并且它只包含原始数据和不可变数据,如 String 和 Enums,那么由于教条的原因,除了 clone 之外的所有内容都将完全浪费时间。请记住克隆做什么和不做什么! (没有深度克隆)

标签: java clone cloning cloneable


【解决方案1】:

你一定要使用clone吗?大多数人都同意 Java 的 clone 已损坏。

Josh Bloch on Design - Copy Constructor versus Cloning

如果你读过我书中关于克隆的内容,尤其是如果你读到字里行间,你就会知道我认为clone 被深深地破坏了。 [...] 很遗憾 Cloneable 坏了,但它确实发生了。

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

他接着写了几页关于如果你觉得必须的话,你应该如何实现clone。但他以这个结尾:

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

重点是他的,不是我的。


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


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

【讨论】:

  • 不幸的是,该项目已经使用克隆方法编写,否则我绝对不会使用它。我完全同意你的观点,Java 的克隆实现是 fakakta。
  • 如果一个类及其所有超类在其克隆方法中调用super.clone(),则子类通常只需在添加内容需要克隆的新字段时覆盖clone()。如果任何超类使用new 而不是super.clone(),则所有子类都必须覆盖clone(),无论它们是否添加任何新字段。
【解决方案2】:

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

public MyObject (MyObject toClone) {
}

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 查看@polygenelubricants 的回答,了解为什么这可能是个坏主意。
    • @KarlRichter 我已经阅读了他们的答案。它并没有说这是一个坏主意,除了 Cloneable 通常是一个坏主意,正如有效 Java 中所解释的那样。 OP 已经表示他们必须使用Cloneable。所以我不知道我的答案还有什么可以改进的,除了可能直接删除它。
    【解决方案4】:

    有两种情况会抛出CloneNotSupportedException

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

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

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


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

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

    【讨论】:

    • 如果一个对象公开了一个公共克隆方法,任何不支持它的派生对象都会违反里氏替换原则。如果克隆方法受到保护,我认为最好用其他方法而不是返回正确类型的方法来隐藏它,以防止子类甚至尝试调用super.clone()
    【解决方案5】:

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

    【讨论】:

    • 考虑到我已经在使用 GSON,这是一个很棒且显而易见的解决方案。
    【解决方案6】:

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

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

    【讨论】:

    • 这在大多数情况下都不起作用。你不能对getMySecondMember()返回的对象调用clone(),除非它有public clone方法。
    • 显然,您需要在希望能够克隆的每个对象上实现此模式,或者找到另一种方法来深度克隆每个成员。
    • 是的,这是必要的要求。
    【解决方案7】:

    尽管这里的大多数答案都是有效的,但我需要告诉您,您的解决方案也是实际的 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);
        }
    }
    

    正如您已经注意到和其他人提到的,如果您声明实现了Cloneable 接口,CloneNotSupportedException 几乎没有机会被抛出。

    另外,如果您在被覆盖的方法中没有做任何新的事情,则不需要覆盖该方法。仅当需要对对象进行额外操作或需要将其公开时才需要覆盖它。

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

    【讨论】:

      【解决方案8】:
      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社区的任何人都可以评论这种方法吗?我想这不必要地将克隆和序列化 - 两个完全不同的概念联系起来。我会等着看其他人对此有什么看法。
      • 它没有进入文件系统,它在主内存中(ByteArrayOutputStream)。对于深度嵌套的对象,它的解决方案效果很好。特别是如果您不经常需要它,例如在单元测试中。其中性能不是主要目标。
      【解决方案9】:

      仅仅因为 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 或其他花哨的代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-15
        • 1970-01-01
        • 1970-01-01
        • 2015-09-16
        • 1970-01-01
        • 1970-01-01
        • 2016-03-07
        • 1970-01-01
        相关资源
        最近更新 更多