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
}