面試的時候,經常會遇到這樣的考題:給你兩個類的程式,它們之間是繼承的關系,每個類裡只有建構子方法和一些變數,建構子裡可能還有一段程式對變數值進行了某種運算,另外還有一些將變數值輸出到控制台的程式,然後讓我們判斷輸出的
結果。這實際上是在考查我們對於繼承情況下類的初始化順序的了解。
我們大家都知道,對於靜態變數、靜態初始化、變數、初始化、建構子,它們的初始化順序以此是:
(靜態變數、靜態初始化) > (變數、初始化) > 建構子
我們也可以通過下面的測試程式來驗證這一點:
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 = "父--變數"; // 靜態初始化 static { 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 = "子--變數"; // 靜態初始化 static { System.out.println(s_StaticField); System.out.println("子--靜態初始化"); } // 初始化 { System.out.println(s_Field); System.out.println("子--初始化"); } // 建構子 public SubClass() { System.out.println("子--建構子"); } public static void main(String[] args) { new SubClass(); } }
運行一下上面的程式,結果馬上呈現在我們的眼前:
父--靜態變數 父--靜態初始化 子--靜態變數 子--靜態初始化 父--變數 父--初始化 父--建構子 子--變數 子--初始化 子--建構子
現在,結果已經不言自明了。大家可能會注意到一點,那就是,並不是父類完全初始化完畢後才進行子類的初始化,實際上子類的靜態變數和靜態初始化的初始化是在父類的變數、初始化和建構子初始化之前就完成了。那麼對於靜態變數和靜態初始化之間、變數和初始化之間的先後順序又是怎樣呢?是否靜態變數總是先於靜態初始化,變數總是先於初始化就被初始化了呢?實際上這取決於它們在類中出現的先後順序。
我們以靜態變數和靜態初始化為例來進行說明。 同樣,我們還是寫一個類來進行測試:
public class TestOrder { // 靜態變數 public static TestA a = new TestA(); // 靜態初始化 static { System.out.println("靜態初始化"); } // 靜態變數 public static TestB b = new TestB(); public static void main(String[] args) { new TestOrder(); } } class TestA { public TestA() { System.out.println("TestA"); } } class TestB { public TestB() { System.out.println("TestB"); } }
運行上面的程式,會得到如下的結果:
TestA 靜態初始化 TestB
大家可以隨意改變變數 a、變數 b 以及靜態初始化的前後位置,就會發現輸出結果隨著它們在類中出現的前後順序而改變,這就說明靜態變數和靜態初始化是依照他們在類中的定義順序進行初始化的。同樣,變數和初始化也遵循這個規律。了解了繼承情況下類的初始化順序之後,如何判斷最終輸出結果就迎刃而解了。
測試函數:
public class TestStaticCon { public static int a = 0; static { a = 10; System.out.println("靜態初始化 a = " + a); } { a = 8; System.out.println("初始化 a = " + a); } public TestStaticCon() { this(a); System.out.println("無參數建構子 a = " + a); } public TestStaticCon(int n) { System.out.println("參數建構子 n = " + n); System.out.println("參數建構子 a = " + a); } public static void main(String[] args) { new TestStaticCon(); } }
運行結果:
靜態初始化 a = 10 初始化 a = 8 參數建構子 n = 10 參數建構子 a = 8 無參數建構子 a = 8
結論:
靜態初始化是在類加載時自動執行的,非靜態初始化是在創建對像時自動執行的程式,不創建對像不執行該類的非靜態初始化。且執行順序為
靜態初始化 -> 非靜態初始化 -> 建構子
擴展( 靜態初始化 與 靜態方法 ):
一般情況下,如果有些程式必須在項目啟動的時候就執行的時候,需要使用靜態初始化,這種程式是主動執行的。
需要在項目啟動的時候就初始化,在不創建對像的情況下,其他程序來調用的時候,需要使用靜態方法,這種程式是被動執行的。
兩者的區別就是:靜態初始化是自動執行的。靜態方法是被調用的時候才執行的。
作用:
靜態初始化可用來初始化一些項目最常用的變數或類別靜態方法可用作不創建物件也可能需要執行的程式。
阿裡筆試題:
public class Test1 { public static int k = 0; public static Test1 t1 = new Test1("obj t1"); public static Test1 t2 = new Test1("obj t2"); public static int i = print("var I"); public static int n = 99; public int j = print("var J"); static{ print("static"); } { print("init"); } public Test1(String str){ print(str); ++i; ++n; } public static int print(String str){ k++; System.out.printf("%-3s: %-8s i=%-4d n=%d", k, str, i, n); ++n; return ++i; } public static void main(String[] args) { new Test1("main"); } }
運行結果:
1 : var J i=0 n=0 2 : init i=1 n=1 3 : obj t1 i=2 n=2 4 : var J i=4 n=4 5 : init i=5 n=5 6 : obj t2 i=6 n=6 7 : var I i=8 n=8 8 : static i=9 n=99 9 : var J i=10 n=100 10 : init i=11 n=101 11 : main i=12 n=102
沒有留言:
張貼留言
你好!歡迎你在我的 Blog 上留下你寶貴的意見。