【问题标题】:Overriding Clone without calling the super.clone覆盖克隆而不调用 super.clone
【发布时间】:2013-12-02 09:49:49
【问题描述】:

重写clone 方法,不实现Cloneable 接口并且不调用super.clone() 是否是一个好习惯。这样CloneNotSupportedException就不会抛出异常了。

考虑这个类:

class Money {

    private BigDecimal x ;

    public Object clone() {
        Money m1 = new Money();
        m1.setX(this.x);
        return m1;
    }

    public BigDecimal getX() {
        return x;
    }

    public void setX(BigDecimal x) {
        this.x = x;
    }

}        

这个类,不会抛出CloneNotSupportedException,它就像一个复制构造函数。

这是一个好方法吗?

【问题讨论】:

  • 最好忽略clone 的存在而编写自己的copy 方法。

标签: java clone


【解决方案1】:

你有你的克隆逻辑,当然你知道你的类支持克隆,因此clone方法不应该抛出CloneNotSupportedException。因此,在这里调用super.clone() 会导致您编写样板文件try/catch 并在AssertionError 中重新抛出CloneNotSupportedException,这显然不会被抛出。这个标记Cloneable...我认为,Java 的这一部分设计错误。所以我只是忽略文档并手动复制字段。

使用super.clone() 的唯一两个参数是性能(我想,类似memcpy 在内部使用)和添加新字段时对错误的持续性。

【讨论】:

    【解决方案2】:

    不致电super.clone() 是不好的做法。有关如何实现克隆方法的更多信息,请参阅this answer

    【讨论】:

    • 顺便说一句:请注意 BigDecimal 对象是不可变的,因此在您的示例中,浅拷贝就足够了。
    • 同意“不好的做法”,但我想指出真正的问题:比如子类化,或者添加一个字段。
    【解决方案3】:

    在您的情况下,它是可行的,但在某些情况下,它甚至不起作用。 在不调用super.clone 的情况下实现clone 方法会使子类无法克隆。例如,假设我有一个Fruit

    public class Fruit implements Cloneable {
    
        private String fruitName;
    
        public Fruit(String fruitName) {
            this.fruitName = fruitName;
        }
    
        public String getFruitName() {
            return fruitName;
        }
    
        public Fruit clone() {
            return new Fruit(fruitName);
        }
    }
    

    如您所见,我实现了Fruit#clone 方法而不调用super.clone,这看起来很好。但现在我想创建一个扩展Fruit 的类Apple,如下所示:

    public class Apple extends Fruit {
    
        private String origin;
    
        public Apple(String fruitName, String origin) {
            super(fruitName);
            this.origin = origin;
        }
    
        public Apple clone() {
            // TODO
        }
    
    }
    

    现在的问题是,如何实现苹果的clone 方法。我应该在 Apple 的 clone 方法中调用 super.clone 吗?是的,你应该,否则你无法获得fruitName 的值,这是Fruit 内部的一个私有字段。好的,让我们试试这个:

    public Apple clone() {
        return (Apple) super.clone();
    }
    

    但是,它没有用。因为Fruit#clone没有调用super.clone,所以super.clone()Apple#clone里面的结果返回了Fruit的实例而不是AppleFruit不能转换成Apple,会报错:

    Exception in thread "main" java.lang.ClassCastException: Fruit cannot be cast to Apple
        at Apple.clone(Apple.java:20)
    

    如您所见,您无法为Apple 提供可行的clone 方法,无论您是否调用super.clone,它都不会起作用。只是因为你没有在Fruit#clone中调用super.clone

    总之,如果您希望子类可克隆,则需要在clone 方法中调用super.clone

    【讨论】:

      【解决方案4】:

      在您的情况下,您有一个具有clone 方法的类,而没有告诉外界它实际上是Cloneable。这不是克隆,而是原型。

      如果你想clone使用接口,如果不选择其他方法名。

      请同时查看文档:

      创建并返回此对象的副本。的确切含义 “复制”可能取决于对象的类别。总体意图是 即,对于任何对象 x,表达式:x.clone() != x 将为真, 并且表达式: x.clone().getClass() == x.getClass() 将是 是的,但这些不是绝对要求。虽然它通常是 如果: x.clone().equals(x) 为真,这不是 绝对要求。按照惯例,返回的对象应该是 通过调用 super.clone 获得。如果一个类及其所有 超类(对象除外)遵守这个约定,就是这样 那 x.clone().getClass() == x.getClass()。

      你必须打电话给super.clone()是有原因的。

      【讨论】:

      • 您的报价确认了 OP 的实施不会违反合同。
      • 按照约定,返回的对象应该通过调用super.clone来获取
      • 如果有人想要真正覆盖clone,会发生什么?
      • 我的意思是,OP 没有违反合同。他偏离了惯例,但这是另一回事。例如,如果他的班级是final,则甚至无法从外部确定他是否依赖于super.clone()。没有人会被不可见的实现细节弄糊涂。
      • 我看不出这个问题有什么问题。他在问“这是否是一个好方法”。所以我的回答是“如果你打算让你的班级final,那完全没问题。”
      【解决方案5】:

      克隆是关于应对/复制对象的类型和状态。如果我们必须识别类型并且将其与 Object 类关联是有意义的。 Java本质上是通过引用和浅拷贝原则来操作的。如果我们必须启用深拷贝,那么用户必须在 clone() 中指明运行时特殊处理。这是通过使用 Clonable 接口进行标记来完成的。

      【讨论】:

        【解决方案6】:

        理论上添加对克隆的覆盖是强制执行原型模式的正确方法,即在运行时创建对象的副本而不知道类型。

        实际上这是一个不同的故事。克隆是执行此操作的 API 标准方法。当您为独立类编写自己的克隆方法时,可以放弃 super.clone 调用并继续。但这是一种幼稚的做法。假设您有一个遵循继承机制的类。想象一下,狮子是食肉动物,因此又是动物。现在,如果您将 super.clone 放到 Lion 中,您最终可能会重新创建 Lion 的副本,而不是 Carnivore 或上帝禁止甚至是 Animal。

        因此,最好遵循 API 标准,避免出现任何问题。

        【讨论】:

          猜你喜欢
          • 2011-12-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-20
          • 1970-01-01
          • 1970-01-01
          • 2012-05-16
          • 2016-03-26
          相关资源
          最近更新 更多