JAVA构造时成员初始化的陷阱

版权声明:作者:N3verL4nd 出处: https://blog.csdn.net/lgh1992314/article/details/80219496
class Base {
    Base() {
        preProcess();
    }

    void preProcess() {
        System.out.println("Base::preProcess");
    }
}

class Derived extends Base {
    public String whenAmISet = "set when declared";

    @Override
    void preProcess() {
        System.out.println(whenAmISet);
        whenAmISet = "set in preProcess()";
    }
}

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

输出

null
set when declared

解释:

Derived d = new Derived();

Java 语言层面的 new 它完成分配空间与调用构造器的任务。
对应的字节码:

0: new           #2    // class Derived
3: dup
4: invokespecial #3    // Method Derived."<init>":()V

new 字节码指令的作用是创建指定类型的对象实例、对其进行默认初始化,并且将指向该实例的一个引用压入操作数栈顶;
dup 指令的作用是复制之前分配的 Derived 空间的引用并压入栈顶。
invokespecial 指令的作用是调用虚拟机合成的<init>方法。包括:构造方法的调用和属性的初始化。invokespecial 需要消耗一个栈顶元素,也就是一个指向 Derived 实例的引用。

同时由于 Derived 继承于 Base,所以会导致 Base 合成的<init> 方法的执行,以此类推。

Derived();
Code:
0: aload_0
1: invokespecial #1   // Method Base."<init>":()V
4: aload_0
5: ldc           #2   // String set when declared
7: putfield      #3   // Field whenAmISet:Ljava/lang/String;
10: return

可以看到合成的<init> 方法先执行父类的<init>方法(如果存在)然后再执行子类属性的初始化。

于是,Derived 中的 whenAmISet 属性的初始化发生在了 preProcess() 之后。这是因为,Java需要保证父类的初始化早于子类的成员初始化,否则,在子类中使用父类的成员变量就会出现问题。调用完父类的<init>,然后才执行本类属性的初始化。

System.out.println(whenAmISet); 输出 null,也说明 Java 类实例中为什么要执行默认初始化。
因为在构造子类对象的时候,首先调用父类的构造函数,而这时候如果去调用子类的函数,由于子类还没有构造完成,子类的成员尚未初始化,这么做显然是不安全的。

同时也说明,public String whenAmISet = "set when declared"; 并不是一个原子操作。而是分为两步:

  • 为变量 whenAmISet 分配内存
  • 为变量 whenAmISet 执行初始化(在<init> 方法内)

<init>方法的组成包括:调用构造方法和初始化属性。

如:

public class TestString {
  private String a = "hello world";
}

可以等价改写为:

public class TestString {
  private String a;
  public TestString() { // TestString.<init>()V
    super(); // invokespecial java/lang/Object.<init>()V
    this.a = "hello world";
  }
}

D:\N3verL4nd\Desktop>javap -c Main
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Derived
       3: dup
       4: invokespecial #3                  // Method Derived."<init>":()V
       7: astore_1
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: getfield      #5                  // Field Derived.whenAmISet:Ljava/lang/String;
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      18: return
}

D:\N3verL4nd\Desktop>javap -c Derived
Compiled from "Main.java"
class Derived extends Base {
  public java.lang.String whenAmISet;

  Derived();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method Base."<init>":()V
       4: aload_0
       5: ldc           #2                  // String set when declared
       7: putfield      #3                  // Field whenAmISet:Ljava/lang/String;
      10: return

  void preProcess();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #3                  // Field whenAmISet:Ljava/lang/String;
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: aload_0
      11: ldc           #6                  // String set in preProcess()
      13: putfield      #3                  // Field whenAmISet:Ljava/lang/String;
      16: return
}

D:\N3verL4nd\Desktop>javap -c Base
Compiled from "Main.java"
class Base {
  Base();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokevirtual #2                  // Method preProcess:()V
       8: return

  void preProcess();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String Base::preProcess
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

猜你喜欢

转载自blog.csdn.net/lgh1992314/article/details/80219496