Java類的初始化順序
知道Java 類初始化過程有利於我們對其運行過程的理解。
首先:每個類的編譯代碼都存在於它自己的獨立的文件中,該文件只在需要使用程序代碼時才會被加載。通常加載發生在創建類的第一個對象時或者該類的靜態資源被訪問時。
另外,定義為static 類型的代碼只會被初始化一次。
另外,構造器方法實際上是static的,是隱式的static聲明。
首先説結果:
加載順序:該類的頂級父類的靜態代碼塊 -> 頂級父類的靜態成員變量 -> 父類的靜態代碼塊 -> 父類的靜態成員變量 -> ... -> 當前類的靜態代碼塊 -> 當前類的靜態成員變量 -> 頂級父類構造代碼塊初始化 -> ... -> 當前類構造代碼塊初始化 -> 頂級父類構造器初始化 -> ... -> 當前類構造器初始化 -> 運行業務代碼...
一、靜態資源被訪問時的加載的情況
package cn.tobin.cls;
class TestClassInitSuper {
private int i;
protected int j;
TestClassInitSuper() {
System.out.println("i : " + i + "; j : " + j);
System.out.println("TestClassInitSuper 構造器已運行");
}
{
i = 0;
j = 1;
System.out.println("TestClassInitSuper構造代碼塊已運行 ");
}
static {
System.out.println("TestClassInitSuper靜態代碼塊已運行 ");
}
private static String SUPER_STATIC_STR = printStr("TestClassInitSuper.SUPER_STATIC_STR 已初始化");
private static void parentStaticTest1() {
System.out.println("TestClassInitSuper parentStaticTest1靜態方法已運行");
}
static String printStr(String str) {
System.out.println(str);
return str;
}
}
public class TestClassInit extends TestClassInitSuper {
private int k = 2;
private MemberObject member = new MemberObject();
{
System.out.println("TestClassInit構造代碼塊已運行 ");
}
static {
System.out.println("TestClassInit靜態代碼塊已運行 ");
}
public TestClassInit() {
System.out.println("TestClassInit.k : " + k);
System.out.println("TestClassInit 構造器已運行");
}
public static String STATIC_STR = printStr("TestClassInit 靜態成員變量STATIC_STR已初始化");
private static void subStaticTest1() {
System.out.println("TestClassInit subStaticTest1靜態方法已運行");
}
public void test() {
System.out.println("test() 方法運行");
}
public static void main(String[] args) {
// 只調用靜態資源
System.out.println("--- 只調用靜態資源情況下的加載 ---");
System.out.println(TestClassInit.STATIC_STR);
System.out.println("--- 第二次加載TestClassInit ---");
System.out.println(TestClassInit.STATIC_STR);
System.out.println("--- 調用完成 ---");
}
}
package cn.tobin.cls;
public class MemberObject {
public MemberObject() {
System.out.println("初始化了引用成員對象 MemberObject");
}
}
再創建一個類單獨用於測試:
package cn.tobin.cls;
public class TestInit {
public static void main(String[] args) {
// 只調用靜態資源
System.out.println("--- 只調用靜態資源情況下的加載 ---");
System.out.println(TestClassInit.STATIC_STR);
System.out.println("--- 第二次加載TestClassInit ---");
System.out.println(TestClassInit.STATIC_STR);
System.out.println("--- 調用完成 ---");
}
}
運行結果:
--- 只調用靜態資源情況下的加載 ---
TestClassInitSuper靜態代碼塊已運行
TestClassInitSuper.SUPER_STATIC_STR 已初始化
TestClassInit靜態代碼塊已運行
TestClassInit 靜態成員變量STATIC_STR已初始化
TestClassInit 靜態成員變量STATIC_STR已初始化
--- 第二次加載TestClassInit ---
TestClassInit 靜態成員變量STATIC_STR已初始化
--- 調用完成 ---
Process finished with exit code 0
在這個測試中,如果把該main 方法寫在TestClassInit類中,結果就可能不太一樣了:
TestClassInitSuper靜態代碼塊已運行
TestClassInitSuper.SUPER_STATIC_STR 已初始化
TestClassInit靜態代碼塊已運行
TestClassInit 靜態成員變量STATIC_STR已初始化
--- 只調用靜態資源情況下的加載 ---
TestClassInit 靜態成員變量STATIC_STR已初始化
--- 第二次加載TestClassInit ---
TestClassInit 靜態成員變量STATIC_STR已初始化
--- 調用完成 ---
Process finished with exit code 0
原因很簡單,在加載到main方法之前,當前類(TestClassInit)首先被加載,然後再執行main 方法。
二、創建對象時的加載順序
再創建一個類單獨測試,第一次創建對象和第二次創建對象的情況:
package cn.tobin.cls;
public class TestInit2 {
public static void main(String[] args) {
TestClassInit testObject = new TestClassInit();
testObject.test();
System.out.println("--- 第二次加載TestClassInit ---");
TestClassInit testObject2 = new TestClassInit();
testObject2.test();
}
}
運行結果:
TestClassInitSuper靜態代碼塊已運行
TestClassInitSuper.SUPER_STATIC_STR 已初始化
TestClassInit靜態代碼塊已運行
TestClassInit 靜態成員變量STATIC_STR已初始化
TestClassInitSuper構造代碼塊已運行
i : 0; j : 1
TestClassInitSuper 構造器已運行
初始化了引用成員對象 MemberObject
TestClassInit構造代碼塊已運行
TestClassInit.k : 2
TestClassInit 構造器已運行
test() 方法運行
--- 第二次加載TestClassInit ---
TestClassInitSuper構造代碼塊已運行
i : 0; j : 1
TestClassInitSuper 構造器已運行
初始化了引用成員對象 MemberObject
TestClassInit構造代碼塊已運行
TestClassInit.k : 2
TestClassInit 構造器已運行
test() 方法運行
Process finished with exit code 0
説明:父類優於基類優先初始化,每個類內部靜態代碼塊,靜態成員都只初始化一次,類中成員變量優於構造代碼塊,構造器之前,靜態初始化之後初始化,並且每次加載都重新初始化一次。