Java构造时成员初始化的陷阱(Java中的声明和初始化不是一个原子操作)


class Base 

    Base() { 
    System.out.println("1");
        preProcess(); 
    } 
   
    void preProcess() {
    System.out.println("2");
    } 
}
public class Test extends Base 

    public String whenAmISet = "set when declared"; 
   
    @Override void preProcess() 
    { 
    System.out.println("3");
        whenAmISet = "set in preProcess()"; 
    } 

}

public class Main{
public static void main(String[] args) {
Test d = new Test();
System.out.println(d.whenAmISet);

}
}


  1. 进入Derived 构造函数。
  2. Derived 成员变量的内存被分配。
  3. Base 构造函数被隐含调用。
  4. Base 构造函数调用preProcess()。
  5. Derived 的preProcess 设置whenAmISet 值为 "set in preProcess()"。
  6. Derived 的成员变量初始化被调用。
  7. 执行Derived 构造函数体

等一等,这怎么可能?在第6步,Derived 成员的初始化居然在 preProcess() 调用之后?是的,正是这样,我们不能让成员变量的声明和初始化变成一个原子操作,虽然在Java中我们可以把其写在一起,让其看上去像是声明和初始化一体。但这只是假象,我们的错误就在于我们把Java中的声明和初始化看成了一体在C+的世界中,C并不支持成员变量在声明的时候进行初始化,其需要你在构造函数中显式的初始化其成员变量的值,看起来很土,但其实C+用心良苦。
在面向对象的世界中,因为程序以对象的形式出现,导致了我们对程序执行的顺序雾里看花。所以,在面向对象的世界中,程序执行的顺序相当的重要
下面是对上面各个步骤的逐条解释。

  1. 进入构造函数。
  2. 为成员变量分配内存。
  3. 除非你显式地调用super(),否则Java 会在子类的构造函数最前面偷偷地插入super() 。
  4. 调用父类构造函数。
  5. 调用preProcess,因为被子类override,所以调用的是子类的。
  6. 于是,初始化发生在了preProcess()之后。这是因为,Java需要保证父类的初始化早于子类的成员初始化,否则,在子类中使用父类的成员变量就会出现问题。
  7. 正式执行子类的构造函数(当然这是一个空函数,虽然我们没有声明)。


你可能感兴趣的:(Java中的声明和初始化)