很有意思的一篇文章
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();
}
}
運行以上代碼,我們會得到如下的輸出結果:
靜態變量
靜態初始化塊
變量
初始化塊
構造器
這與上文中說的完全符合。那么對於繼承情況下又會怎樣呢?我們仍然以一段測試代碼來獲取最終結果:
Java代碼
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();
}
}
運行一下上面的代碼,結果馬上呈現在我們的眼前:
父類--靜態變量
父類--靜態初始化塊
子類--靜態變量
子類--靜態初始化塊
父類--變量
父類--初始化塊
父類--構造器
子類--變量
子類--初始化塊
子類--構造器
現在,結果已經不言自明了。大家可能會注意到一點,那就是,並不是父類完全初始化完畢后才進行子類的初始化,實際上子類的靜態變量和靜態初始化塊的初始化是在父類的變量、初始化塊和構造器初始化之前就完成了。
那么對於靜態變量和靜態初始化塊之間、變量和初始化塊之間的先后順序又是怎樣呢?是否靜態變量總是先於靜態初始化塊,變量總是先於初始化塊就被初始化了呢?實際上這取決於它們在類中出現的先后順序。我們以靜態變量和靜態初始化塊為例來進行說明。
同樣,我們還是寫一個類來進行測試:
Java代碼
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("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方法的指令相同。
在虛擬機中存在三種方法的調用指令,這三種調用指令在效率上不同。
接口方法的指令調用,這種調用速度最慢。
普通類方法的調用指令,這種調用速度中等。
構造方法與私有方法調用指令,這種調用速度最快。
