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

Java 访问构造函数中的实例成员,java,constructor,instance-variables,super,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; pu

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

我偶然发现了以下代码:

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 一百

我的问题是:

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

我错过了什么

即使父级的构造函数尚未完成执行,它仍然能够访问子级的未初始化实例变量i

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

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

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

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


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

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


因此,输出应该并且将是01100

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

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 + " ");
    }
}

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

遗憾的是,您不能通过将构造函数编写为

Child() {
  i=100;
  super();
}
如果不能做到这一点,就无法在父构造函数的重写方法调用中使用childs
i
字段之前设置它

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

另一种方法是将
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
与父/子继承人分离。那你可以 在调用父构造函数之前创建打印机。(通常,这种技巧可以用来完全去除子类,只留下父类和组件——也就是说,最终使用的是组合而不是继承。)


“一旦构造开始,它们就可以随时访问。但是,它们直到超级构造函数运行之后才被初始化。”---这一点很好+1.
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() );
   }
}