15

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

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

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

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

4

5 回答 5

16

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

  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()会立即转换为Fruit. 这允许继承方法随后修改 Fruit 特定的成员数据(fBestBeforeDate在本例中)。

因此,对子clone()方法的调用虽然会调用父方法的克隆,但也会将其自己的特定修改添加到新制作的副本中。在这种情况下,结果将是 a Fruit,而不是Object

现在,更重要的是,克隆是个坏主意。复制构造函数和工厂提供了更直观且易于维护的替代方案。尝试阅读我附加到示例的Java 实践链接上的标题:它总结了一些问题。Josh Bloch 也有更长的讨论:绝对应该避免克隆。以下是关于他为什么认为克隆是一个问题的精彩摘要段落:

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

于 2012-08-10T16:38:31.547 回答
4

它是一种特殊的本地方法。这样做是为了使克隆更容易。否则,您将不得不复制祖先类的整个代码。

于 2012-08-10T16:32:10.257 回答
4

尽管接受了一个答案,但我认为它不能完全回答问题的第一部分(为什么子类中的向下转换总是有效的)。虽然我不能真正解释它,但我想我可以澄清一些与我相同的海报的困惑。我们有以下课程

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

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

于 2014-06-04T10:28:36.047 回答
3

如果 B 中的 clone() 返回 A 中的 clone() 返回值,C 中的 clone() 返回 B 中的 clone() 返回值,那么 C 中的 clone() 将返回 A 中的 clone() 返回值。

于 2012-08-10T16:30:51.497 回答
0

这个类有关于这个类的信息以及它与另一个类相关联。因此,从概念上讲,这个类的对象也将具有关联类的信息。此对象是不完整的对象,没有关联的对象/父类。需要复制此类中的所有直接和间接字段,使其成为当前对象的值得新克隆。我们不能只访问仅表示子部分的参考部分。

于 2019-01-14T09:12:28.877 回答