Java:为什么由 super class 初始化的变量在创建对象后保持值

Java: Why a variable initialised by super class keeps value once object is created

我在工作中提出了一个问题,并简化了代码以重现它

public class Test {
    public void callTest(){ return; }
    public Test() { callTest(); }
}

public class Test1 extends Test{
    private Integer myInt;
    private Integer myNullInt = null;

    public Test1() {
      super();
    }

    @Override
    public void callTest() {
      myInt     = 25;
      myNullInt = 30;
    }

    @Override
    public String toString() {
      return "Test1{myInt=" + myInt + ", myNullInt=" + myNullInt +'}';
    }

    public static void main(String[] args) {
      Test1 t1 = new Test1();
      System.out.println(t1);
    }
}

结果是:Test1{myInt=25, myNullInt=null}

根据我的理解,实例变量初始值设定项应该在调用 super() 之后执行。 那么为什么 'myInt' 得到一个值。 甚至这怎么可能?

您的代码中的执行顺序如下所示。

  1. Test1 的构造函数被调用。
  2. super 调用 Test.
  3. 中的构造函数
  4. 由于调用是从 Test1 开始的,因此调用了被覆盖的 callTest,它将 myInt 的值设置为 25,将 myNullInt 的值设置为 30。
  5. super 已完成,现在继续创建 Test1 对象。
  6. myNullInt 的实例变量被覆盖为 null。由于myInt没有指令,所以数值不变。
  7. sysout 调用调用 toString 并打印 Test1 值。

声明实例变量的方式在继承中很重要。

实例初始化程序在调用 super(); 之后执行 - 这就是 myNullInt 为空的原因。 myInt 不涉及实例初始值设定项。

事件的顺序是这样的:

  • main 方法创建一个 new Test1() 对象
  • 也就是说调用了Test1的构造函数
  • 该构造函数依次调用 super 构造函数 Test()
    • Test的构造函数调用callTest();方法
    • 由于正在构建的对象是Test1对象,所以调用Test1中的callTest方法
    • Test1.callTest()myInt 字段设置为 25,将 myNullInt 字段设置为 30
    • 之后Test的构造函数结束
  • Test1 的构造函数恢复并执行字段初始化程序 myNullInt = null;
  • 因为 myInt 没有初始化器,所以它的值没有改变

Java 语言规范中详细描述了该过程(包括使用默认值初始化),Creation of New Class Instances:

Whenever a new class instance is created, memory space is allocated for it with room for all the instance variables declared in the class type and all the instance variables declared in each superclass of the class type, including all the instance variables that may be hidden (§8.3).

[If there is not sufficient space ...] Otherwise, all the instance variables in the new object, including those declared in superclasses, are initialized to their default values (§4.12.5).

Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:

  1. / 2. [...]
  1. [...] an explicit or implicit invocation of a superclass constructor [...]
  1. Execute the instance initializers and instance variable initializers for this class [...]
  1. Execute the rest of the body of this constructor. [...]