天天看點

Java類的初始化順序 (靜态變量、靜态初始化塊、變量、初始...

很有意思的一篇文章

1.沒有繼承

靜态變量->靜态初始化塊->變量->變量初始化塊->構造方法

2.有繼承的情況

父類靜态變量->父類靜态初始化塊->子類靜态變量->子類靜态變量初始化塊->父類變量初始化->父類變量初始化塊->父類構造方法->子類變量初始化->子類變量初始化塊->子類構造方法

--------------------------------------------------我是copy分割線---------------------------------------------------------

大家在去參加面試的時候,經常會遇到這樣的考題:給你兩個類的代碼,它們之間是繼承的關系,每個類裡隻有構造器方法和一些變量,構造器裡可能還有一段代碼對變量值進行了某種運算,另外還有一些将變量值輸出到控制台的代碼,然後讓我們判斷輸出的結果。這實際上是在考查我們對于繼承情況下類的初始化順序的了解。 

我們大家都知道,對于靜态變量、靜态初始化塊、變量、初始化塊、構造器,它們的初始化順序以此是(靜态變量、靜态初始化塊)>(變量、初始化塊)>構造器。我們也可以通過下面的測試代碼來驗證這一點: 

Java代碼 

public class InitialOrderTest { 

// 靜态變量 

public static String staticField = "靜态變量"; 

// 變量 

public String field = "變量"; 

// 靜态初始化塊 

static { 

System.out.println(staticField); 

System.out.println("靜态初始化塊"); 

// 初始化塊 

System.out.println(field); 

System.out.println("初始化塊"); 

// 構造器 

public InitialOrderTest() { 

System.out.println("構造器"); 

public static void main(String[] args) { 

new InitialOrderTest(); 

運作以上代碼,我們會得到如下的輸出結果: 

靜态變量 

靜态初始化塊 

變量 

初始化塊 

構造器 

這與上文中說的完全符合。那麼對于繼承情況下又會怎樣呢?我們仍然以一段測試代碼來擷取最終結果: 

class Parent { 

public static String p_StaticField = "父類--靜态變量"; 

public String p_Field = "父類--變量"; 

System.out.println(p_StaticField); 

System.out.println("父類--靜态初始化塊"); 

System.out.println(p_Field); 

System.out.println("父類--初始化塊"); 

public Parent() { 

System.out.println("父類--構造器"); 

public class SubClass extends Parent { 

public static String s_StaticField = "子類--靜态變量"; 

public String s_Field = "子類--變量"; 

System.out.println(s_StaticField); 

System.out.println("子類--靜态初始化塊"); 

System.out.println(s_Field); 

System.out.println("子類--初始化塊"); 

public SubClass() { 

System.out.println("子類--構造器"); 

// 程式入口 

new SubClass(); 

運作一下上面的代碼,結果馬上呈現在我們的眼前: 

父類--靜态變量 

父類--靜态初始化塊 

子類--靜态變量 

子類--靜态初始化塊 

父類--變量 

父類--初始化塊 

父類--構造器 

子類--變量 

子類--初始化塊 

子類--構造器 

現在,結果已經不言自明了。大家可能會注意到一點,那就是,并不是父類完全初始化完畢後才進行子類的初始化,實際上子類的靜态變量和靜态初始化塊的初始化是在父類的變量、初始化塊和構造器初始化之前就完成了。 

那麼對于靜态變量和靜态初始化塊之間、變量和初始化塊之間的先後順序又是怎樣呢?是否靜态變量總是先于靜态初始化塊,變量總是先于初始化塊就被初始化了呢?實際上這取決于它們在類中出現的先後順序。我們以靜态變量和靜态初始化塊為例來進行說明。 

同樣,我們還是寫一個類來進行測試: 

public class TestOrder { 

public static TestA a = new TestA(); 

public static TestB b = new TestB(); 

new TestOrder(); 

class TestA { 

public TestA() { 

System.out.println("Test--A"); 

class TestB { 

public TestB() { 

System.out.println("Test--B"); 

運作上面的代碼,會得到如下的結果: 

Test--A 

Test--B 

大家可以随意改變變量a、變量b以及靜态初始化塊的前後位置,就會發現輸出結果随着它們在類中出現的前後順序而改變,這就說明靜态變量和靜态初始化塊是依照他們在類中的定義順序進行初始化的。同樣,變量和初始化塊也遵循這個規律。

了解了繼承情況下類的初始化順序之後,如何判斷最終輸出結果就迎刃而解了。

靜态塊與靜态成員的初始化工作與執行個體化過程無關,執行個體化必須先執行靜态塊和靜态成員,但并不代表執行個體化一定會執行靜态塊和靜态成員。隻有當執行個體化的對應的類為加載入虛拟機的時候,才會進行這種操作。有些時候執行靜态塊或者初始化靜态成員不一定就是執行個體化該類對象才會進行的,例如調研該類的某靜态成員或者靜态方法,又例如該類的子類被執行個體化或者調用了靜态成員或靜态方法等。

還有執行個體化的實際順序其實是(省略類初始化過程)

1、進入目前類構造方法。

2、進入父類構造方法遞歸直到java.lang.Object類構造方法。

3、執行java.lang.Object類構造方法,順序依次為成員變量初始與初始化塊(安裝上下文順序),對應調用的構造方法體。

4、執行java.lang.Object類的直接子類的構造函數,這個過程遞歸到目前類。

5、目前類執行順序與前面java.lang.Object類相同。

構造方法的本質其實就是一個普通的無傳回參數的名字叫做<init>的方法,不過虛拟機調用這個方法的指令與其它方法不同而已,它的調用指令與調用private方法的指令相同。

在虛拟機中存在三種方法的調用指令,這三種調用指令在效率上不同。

接口方法的指令調用,這種調用速度最慢。

普通類方法的調用指令,這種調用速度中等。

構造方法與私有方法調用指令,這種調用速度最快。