访问构造函数中的实例成员

时间:2012-08-27 05:17:57

标签: java constructor instance-variables super

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

我偶然发现了以下代码:

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的构造函数尚未完成执行?

我错过了什么?

5 个答案:

答案 0 :(得分:7)

  

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

你的书是错的(如果这就是它真正说的那样)。一旦施工开始,它们随时都可以使用。但是,在超级构造函数运行之后,它们才会初始化。所以你打印的是默认值:null,zero或false。

答案 1 :(得分:3)

  

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

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

变量的“空间”已经到位(毕竟你确实有一个实例),但是将它初始化为正确的起始值的代码还没有运行。所以它都是null,false和0。

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

答案 2 :(得分: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 + " ");
    }
}

答案 3 :(得分:0)

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

因此输出应该 0 100

答案 4 :(得分:0)

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

遗憾的是,无法通过将构造函数编写为

来解决此问题
Child() {
  i=100;
  super();
}

如果没有这样做,就无法设置子i字段,然后才能在父项构造函数的覆盖方法调用中使用它。

值得了解一些方法来解决这个问题:

方法是隐藏抽象getter后面的i,并提供一个静态工厂函数,用于创建覆盖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与父/子heirachy分离。然后你可以 在调用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() );
   }
}