4

我在一本书中读到,只有在超级构造函数运行后才能访问实例成员。

我偶然发现了以下代码:

class Parent {

    Parent() {
        printIt();
    }

    void printIt() {
        System.out.println("I'm in a overridden method. Great.");
    }
}

class Child extends Parent {

    int i = 100;

    public static void main(String[] args) {
        Parent p = new Child();
        p.printIt();
    }

    void printIt() {
        System.out.print(i + " ");
    }
}

它打印:

0 100

我的问题是:

如果实例成员只有在超级构造函数运行后才能访问,那为什么在执行Parent类的printIt()方法时(实际上是Child的printIt(),由于多态性),它能够访问未初始化的Child 的实例变量 i 即使 Parent 的构造函数尚未完成执行?

我错过了什么?

4

5 回答 5

8

我在一本书中读到,只有在超级构造函数运行后才能访问实例成员。

你的书是错的(如果它真的这么说的话)。一旦施工开始,它们就可以随时访问。但是,直到超级构造函数运行之后,它们才会被初始化。因此,您打印的是默认值:null、零或 false。

于 2012-08-27T05:23:52.057 回答
4

即使 Parent 的构造函数尚未完成执行,它也能够访问 Child 的未初始化实例变量 i?

您可以访问它,但在它被初始化之前(这不是您通常想要的)。

变量的“空格”已经到位(毕竟您确实有一个实例),但是将其初始化为其正确起始值的代码尚未运行。所以它将全部为 null、false 和 0。

因此,类中的一个方法(“printIt”)在对象生命周期的一个尴尬点被调用(在初始化程序运行之前,在“半完成”实例上)。这就是您阅读的警告想要说的。

于 2012-08-27T05:20:20.810 回答
2

我认为你的例子误导了你。事实上,超级构造函数之前运行过,你可以通过下面的修改示例看到这一点。另外澄清一下,成员值是可访问的,但它们可能尚未初始化。

class Parent {

    int i = 0;

    Parent() {
        i = 1;
        printIt();
    }

    void printIt() {
        System.out.println("I'm in a overridden method. Great. i = " + i);
    }
}

class Child extends Parent {
    public static void main(String[] args) {
        Parent p = new Child();
        p.printIt();
    }

    void printIt() {
        System.out.print(i + " ");
    }
}
于 2012-08-27T05:28:03.603 回答
0

覆盖发生在您的代码中。在运行时考虑对象。因此,调用了 Child 的 printIt()。此时,“i”的值未知,但默认值为“0”,因为它是一个实例变量。完成后,调用 p.printIt(),调用 Child 的 printIt(),此时读取 int i=100 并打印 100。

因此输出应该并且将是0 100

于 2012-08-27T05:27:55.203 回答
0

首次创建对象时,对象中的字段被初始化为默认值 null 或 0,然后您的构造函数实际运行,第一步调用超级构造函数。

可悲的是,您无法通过将构造函数编写为来解决此问题

Child() {
  i=100;
  super();
}

如果无法做到这一点,就无法在 childsi字段用于父构造函数的覆盖方法调用之前设置它。

不过,值得了解一些解决此问题的方法:

一种方法是隐藏i在一个抽象的 getter 后面,并提供一个静态工厂函数来创建新的实例覆盖getI

public class Child extends Parent {

   protected abstract getI();

   @Override void printIt() {
     System.out.print("i = " + i);
   }

   static Child create(final int i) {
      return new Child() {
         int getI() { return i; }
      }
   }
}

Child child = Child.create(100);

另一种方法是printIt从父/子层次结构中分离出来。然后您可以在调用 Parent 构造函数之前创建打印机。(通常这种技巧可以用来彻底清除 Child ,只剩下 Parent 类和组件 -您最终使用组合而不是继承。)

class Parent {
   public interface Printer {
     void printIt();
   }

   public class DefaultPrinter extends Printer {
     @Override void printIt() { 
       System.out.println("Default Printer...");
     }
   }

   Parent() {
     this(new DefaultPrinter());
   }

   Parent(Printer p ) {
     this.printer = p;
     printIt();
   }

   void printIt() {
     p.printIt();
   }
}

public class Child extends Parent {
   public class ChildPrinter implements Parent.Printer {
     final int i = 100;
     @Override void printIt() {
       System.out.println("i = "+i);
     }
   }

   Child() {
     super( new Printer() );
   }
}
于 2014-07-29T03:17:51.937 回答