Java:super.clone() 方法和继承

2022-09-03 12:34:55

我有一个关于Java中的方法的快速问题,用于继承 - 我从按钮一直调用父类中的方法。clone()super.clone()clone()

该方法应该返回此对象的副本,但是如果我在继承 heirachy 中有三个类并调用三次,为什么继承 heirachy 中的最高类(就在类 Object 下)不返回该类的副本?clone()super.clone()

假设我们有三个类:A,B和C,其中A ->B - >C(继承= ->)

然后在类C中调用,在B中调用,在A中调用,调用A调用“这次Object.clone()被调用”。为什么它不是从 中返回的与类 A 相关的对象的副本?这在我看来是合乎逻辑的。super.clone()clone()super.clone()clone()super.clone()thisObject.clone()


答案 1

听起来这里至少有两个问题在起作用:

  1. 听起来你对 clone() 通常如何实现感到困惑。

  2. 这听起来像是你认为克隆是一个好主意(与使用复制构造函数,工厂或等效项相比)。

下面是克隆方法实现的示例:

@Override 
public Object clone() throws CloneNotSupportedException {   
    //get initial bit-by-bit copy, which handles all immutable fields
    Fruit result = (Fruit)super.clone();

    //mutable fields need to be made independent of this object, for reasons
    //similar to those for defensive copies - to prevent unwanted access to
    //this object's internal state
    result.fBestBeforeDate = new Date( this.fBestBeforeDate.getTime() );

    return result;
}

请注意,的结果会立即转换为 .这允许继承方法随后修改特定于水果的成员数据(在本例中)。super.clone()FruitfBestBeforeDate

因此,对子方法的调用虽然会调用父级的克隆,但也会将其自己的特定修改添加到新创建的副本中。在这种情况下,出来的将是 一个 ,而不是一个 .clone()FruitObject

现在,更重要的是,克隆是一个坏主意。复制构造函数和工厂提供了更直观且易于维护的替代方法。尝试阅读我附加到示例的 Java 实践链接上的标头:它总结了一些问题。乔希·布洛赫(Josh Bloch)也有一个更长的讨论:克隆绝对应该避免。这里有一个很好的总结段落,说明为什么他认为克隆是一个问题:

对象的克隆方法非常棘手。它基于现场副本,并且是“语言外的”。它创建一个对象而不调用构造函数。不能保证它保留构造函数建立的不变量。多年来,无论是在 Sun 内部还是外部,都有很多 bug,这是因为如果你只是在链上反复调用 super.clone,直到你克隆了一个对象,你就有了一个对象的浅副本。克隆通常与要克隆的对象共享状态。如果该状态是可变的,则没有两个独立的对象。如果修改一个,另一个也会更改。突然之间,你得到了随机的行为。


答案 2

虽然一个答案被接受,但我认为它并不能完全回答问题的第一部分(为什么子类中的下放总是有效的)。虽然我无法真正解释它,但我认为我可以澄清海报的一些困惑,这与我的一样。我们有以下课程

class A implements Cloneable 
{
   @Override
   protected A clone() throws CloneNotSupportedException // could be public
   { 
      Object clone = super.clone();
      System.out.println("Class A: " + clone.getClass()); // will print 'C'
      return (A) clone;
   }
}

class B extends A
{
   @Override
   protected B clone() throws CloneNotSupportedException
   { 
      A clone = super.clone();
      System.out.println("Class B: " + clone.getClass()); // will print 'C'
      return (B) clone;
   }
}

class C extends B
{
   @Override
   protected C clone() throws CloneNotSupportedException
   { 
      B clone = super.clone();
      System.out.println("Class C: " + clone.getClass()); // will print 'C'
      return (C) clone;
   }
}

static main(char[] argv)
{
   C c = new C();
   C cloned_c = c.clone();
}

这样做的结果是

Class A: C

Class B: C

Class C: C

打印在命令行上。因此,事实上,某种方法可以向下查看调用堆栈,并查看在链的开头调用哪种类型的对象,然后,如果调用气泡向上,以便实际调用,则创建该类型的对象。因此,这在课堂上已经发生过,这很奇怪,但它解释了为什么向下的向下转换不会导致.我已经检查了OpenJDK,似乎这是由一些在本机代码中实现的Java黑魔法实现的。clone()Objectclone()Object#clone()CClassCastException