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

时间:2012-08-10 16:22:49

标签: java object inheritance clone instance

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

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

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

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

5 个答案:

答案 0 :(得分:12)

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

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

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

  3. 以下是克隆方法的an example of an implementation

    @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()方法的调用,虽然它将调用父母的克隆,但也会对新制作的副本添加自己的特定修改。在这种情况下,结果将是Fruit,而不是Object

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

      

    对象的克隆方法非常棘手。它基于现场副本,和   这是“超语言”。它创建一个对象而不调用a   构造函数。无法保证它保留不变量   由建设者建立。有很多错误   多年来,无论是在孙内外,都源于如果你这样的事实   只需要反复调用super.clone直到你克隆了一个   对象,你有一个对象的浅表副本。克隆一般   与正在克隆的对象共享状态。如果那个状态是可变的,   你没有两个独立的对象。如果你修改一个,另一个   变化也是如此。突然之间,你会得到随机行为。

答案 1 :(得分: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()的{​​{1}}方法可以看起来 down 调用堆栈,并在链的开头看到的哪种类型的对象调用Object,然后,如果调用冒泡,以便实际调用clone(),则会创建该类型的对象。所以这已经发生在类Object#clone()中,这很奇怪,但它解释了为什么向下转换不会导致C。我已经检查过OpenJDK,看起来这是由一些用本机代码实现的Java黑魔法。

答案 2 :(得分:3)

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

答案 3 :(得分:2)

如果B中的clone()返回A中的任何clone()返回,C中的clone()返回B中返回的clone(),则C中的clone()将返回A中的clone()返回的任何内容。 / p>

答案 4 :(得分:0)

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