要明白子類和父類的初始化執行順序,只需要知曉以下三點,就不會再弄錯了。
1.創建子類對象時,子類和父類的靜態塊和構造方法的執行順序為:父類靜態塊->子類靜態塊->父類構造器->子類構造器。深入理解為什么是這個順序,可以看我這篇文章:從京東面試題看java類和對象的初始化
2.靜態變量的聲明和賦值,聲明會在靜態塊之前,賦值運算將會合並到靜態塊中,順序和源代碼中的順序一致。舉例如下:
源代碼
public class P {
public static int a = 1;
static {
int b = 2;
}
public static int c = 3;
}
在編譯器編譯后,會變成這樣子
public class P {
public static int a;
public static int c;
static {
a = 1;
int b = 2;
c = 3;
}
}
我們來看,編譯后的字節碼是怎樣的,使用命令可以反編譯類的字節碼:javap -v -p P.class
{
public static int a;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
public static int c;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: iconst_1
1: putstatic #2 // Field a:I
4: iconst_2
5: istore_0
6: iconst_3
7: putstatic #3 // Field c:I
10: return
}
我去掉了編譯器生成的構造方法以及一些無關信息,我們可以看到字節碼中,a、c的聲明在前面(其實這個不是重點),在static{}塊中,pc 0~1兩個指令,為靜態字段a賦值1,pc 4~5兩個指令,為第一個局部變量,也就是變量b賦值2,pc 6~7兩個指令,為靜態字段c賦值3。可以看到合並后的static塊,a的賦值在原靜態塊代碼之前,c的賦值在原靜態塊代碼之后,這個順序和源代碼中ac的聲明順序一致。
3.成員變量的聲明和賦值,與靜態變量相同的是成員變量的賦值也會合並到構造器中,不同的是合並后的順序,成員變量的賦值是在構造器的前面。舉例如下:
源代碼
public class P {
public int a = 1;
public P() {
int b = 2;
}
public int c = 3;
}
編譯后的代碼,會像這個樣子
public class P {
public int a;
public int c;
public P() {
a = 1;
c = 3;
int b = 2;
}
}
再來看看編譯后的字節碼是怎樣的
public int a;
descriptor: I
flags: ACC_PUBLIC
public int c;
descriptor: I
flags: ACC_PUBLIC
public P();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: aload_0
10: iconst_3
11: putfield #3 // Field c:I
14: iconst_2
15: istore_1
16: return
字段a和c的聲明在前面,然后看構造器P()的字節碼,pc 0~1兩個指令,是先調用P的父類Object的構造器,字節碼中的構造器方法使用來表示的。pc 4~6三個指令,為成員變量a賦值1。pc9~11三個指令,為成員變量c賦值3,pc 14~15兩個指令,為下表為1的局部變量賦值為2,也就是局部變量b=2。所以可以看出,成員變量賦值邏輯合並到構造器后,是在調用父類構造器之后,原有構造器代碼之前。
回過頭來,你明白了子類父類初始化各個方法的執行順序,而字段的初始化賦值也是合並到方法里,所以創建子類對象時,子類父類各個部分的執行順序都已了然。
總結:
1.講解了子類父類初始化時方法執行順序,包括的靜態塊和構造器方法,靜態塊也是方法,靜態塊在jvm中的方法名叫。
2.講解了字段的賦值是如何合並到方法中,靜態字段賦值合並到靜態塊中,成員變量賦值合並到構造器方法中。