Java與C++有一個不同之處在于,Java不但有構(gòu)造函數(shù),還有一個”初始化塊“(Initialization Block)的概念。下面探究一下它的執(zhí)行順序與可能的用途。
執(zhí)行順序
首先定義A, B, C三個類用作測試,其中B繼承了A,C又繼承了B,并分別給它們加上靜態(tài)初始化塊、非靜態(tài)初始化塊和構(gòu)造函數(shù),里面都是一句簡單的輸出。
主類Main里面也如法炮制。
class A {
static {
System.out.println("Static init A.");
}
{
System.out.println("Instance init A.");
}
A() {
System.out.println("Constructor A.");
}
}
class B extends A {
static {
System.out.println("Static init B.");
}
{
System.out.println("Instance init B.");
}
B() {
System.out.println("Constructor B.");
}
}
class C extends B {
static {
System.out.println("Static init C.");
}
{
System.out.println("Instance init C.");
}
C() {
System.out.println("Constructor C.");
}
}
public class Main {
static {
System.out.println("Static init Main.");
}
{
System.out.println("Instance init Main.");
}
public Main() {
System.out.println("Constructor Main.");
}
public static void main(String[] args) {
C c = new C();
//B b = new B();
}
}
測試代碼
當(dāng)然這里不使用內(nèi)部類,因?yàn)閮?nèi)部類不能使用靜態(tài)的定義;而用靜態(tài)內(nèi)部類就失去了一般性。
那么可以看到,當(dāng)程序進(jìn)入了main函數(shù),并創(chuàng)建了一個類C的對象之后,輸出是這樣子的:
Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.
觀察上面的輸出,可以觀察到兩個有趣的現(xiàn)象:
Main類是肯定沒有被實(shí)例化過的,但是由于執(zhí)行main入口函數(shù)用到了Main類,于是static初始化塊也被執(zhí)行了;
所有的靜態(tài)初始化塊都優(yōu)先執(zhí)行,其次才是非靜態(tài)的初始化塊和構(gòu)造函數(shù),它們的執(zhí)行順序是:
父類的靜態(tài)初始化塊
子類的靜態(tài)初始化塊
父類的初始化塊
父類的構(gòu)造函數(shù)
子類的初始化塊
子類的構(gòu)造函數(shù)
那么如果有多個實(shí)例化對象,又會不會發(fā)生變化呢?于是在第一個C類的對象后面,再實(shí)例化一個B類的對象,再觀察輸出:
Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
可以發(fā)現(xiàn)這輸出跟前面的基本長得一樣對吧?只是在后面多了4行,那是新的B類對象實(shí)例化時產(chǎn)生的信息,同樣也是父類A的初始化塊和構(gòu)造函數(shù)先執(zhí)行,再輪到子類B的初始化塊和構(gòu)造函數(shù)執(zhí)行;同時還發(fā)現(xiàn),靜態(tài)初始化塊的輸出只出現(xiàn)了一次,也就是說每個類的靜態(tài)初始化塊都只在第一次實(shí)例化該類對象時執(zhí)行一次。
無論如何,初始化塊和構(gòu)造函數(shù)總在一起執(zhí)行是件有趣的事情,讓我們反編譯一下看看吧!
查看生成目錄發(fā)現(xiàn)已經(jīng)生成了4個.class文件,分別是A.class, B.class, C.class, Main.class,先看看Main.class的結(jié)構(gòu)(這里重新注釋了new B):
Compiled from "Main.java"public class Main {? public Main();? ? Code:? ? ? 0: aload_0? ? ? 1: invokespecial #1? ? ? ? ? ? ? ? ? // Method java/lang/Object."":()V? ? ? 4: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;? ? ? 7: ldc? ? ? ? ? #3? ? ? ? ? ? ? ? ? // String Instance init Main.? ? ? 9: invokevirtual #4? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V? ? ? 12: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;? ? ? 15: ldc? ? ? ? ? #5? ? ? ? ? ? ? ? ? // String Constructor Main.? ? ? 17: invokevirtual #4? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V? ? ? 20: return? public static void main(java.lang.String[]);? ? Code:? ? ? 0: new? ? ? ? ? #6? ? ? ? ? ? ? ? ? // class C? ? ? 3: dup? ? ? 4: invokespecial #7? ? ? ? ? ? ? ? ? // Method C."":()V
7: astore_1
8: return
static {};
Code:
0: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc? ? ? ? ? #8? ? ? ? ? ? ? ? ? // String Static init Main.
5: invokevirtual #4? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
Main.class的反編譯結(jié)果
可以看到整個Main類被分成三個部分,static {}部分很顯然,就是我們的static初始化塊,在里面調(diào)用了println并輸出了String“Static init Main.”;而main入口函數(shù)也很清晰,首先新實(shí)例化了一個類C的對象,然后調(diào)用了類C的構(gòu)造函數(shù),最后返回;而上面public Main();的部分就很有意思了,這是類Main的構(gòu)造函數(shù),但我們看到里面調(diào)用了兩次println,分別輸出了String“Instance init Main.”和String“Constructor Main.”。難道初始化塊和構(gòu)造函數(shù)被合并到一起了?
我們再看看C類的反編譯結(jié)果吧:
Compiled from "Main.java"class C extends B {? C();? ? Code:? ? ? 0: aload_0? ? ? 1: invokespecial #1? ? ? ? ? ? ? ? ? // Method B."":()V
4: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc? ? ? ? ? #3? ? ? ? ? ? ? ? ? // String Instance init C.
9: invokevirtual #4? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc? ? ? ? ? #5? ? ? ? ? ? ? ? ? // String Constructor C.
17: invokevirtual #4? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
static {};
Code:
0: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc? ? ? ? ? #6? ? ? ? ? ? ? ? ? // String Static init C.
5: invokevirtual #4? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
C.class的反編譯結(jié)果
靜態(tài)初始化塊仍然單獨(dú)分出一部分,輸出了我們的調(diào)試語句。而另一部分,仍然還是類C的構(gòu)造函數(shù)C();,可以看到它先調(diào)用了父類B的構(gòu)造函數(shù),接著輸出了我們初始化塊中的語句,然后才輸出我們寫在構(gòu)造函數(shù)中的語句,最后返回。多次試驗(yàn)也都是如此。于是我們能夠推斷:初始化塊的代碼是被加入到子類構(gòu)造函數(shù)的前面,父類初始化的后面了。
可能的用途:
既然執(zhí)行順序和大概原理都摸清了,那么就要探討一下初始化塊的可能的用途。
靜態(tài)初始化塊
1. ?用于初始化靜態(tài)成員變量
比如給類C增加一個靜態(tài)成員變量sub,我們在static塊里面給它賦值為5:
class C extends B {
static public int a;
static {
a = 5;
System.out.println("Static init C.");
}
......
}
main函數(shù)里輸出這個靜態(tài)變量C.sub:
publicstaticvoidmain(String[] args) {System.out.println("Value of C.sub: " +C.sub);}
Static init Main.
Static init A.
Static init B.
Static init C.
Value of C.sub:?
符合類被第一次加載時執(zhí)行靜態(tài)初始化塊的結(jié)論,且C.sub被正確賦值為5并輸出了出來。
但是乍一看似乎沒有什么用,因?yàn)殪o態(tài)成員變量在定義時就可以順便賦值了。因此在賦值方面有點(diǎn)雞肋。
2. ?執(zhí)行初始化代碼
比如可以記錄第一次訪問類的日志,或方便單例模式的初始化等。對于單例模式,可以先用static塊初始化一些可能還被其他類訪問的基礎(chǔ)參數(shù),等到真正需要加載大量資源的時候(getInstance)再構(gòu)造單體,在構(gòu)造函數(shù)中加載資源。
非靜態(tài)初始化塊
這個就沒什么好說的了,基本跟構(gòu)造函數(shù)一個功能,但比構(gòu)造函數(shù)先執(zhí)行。最常見的用法應(yīng)該還是代碼復(fù)用,即多個重載構(gòu)造函數(shù)都有若干段相同的代碼,那么可以把這些重復(fù)的代碼拉出來放到初始化塊中,但仍然要注意它的執(zhí)行順序,對順序有嚴(yán)格要求的初始化代碼就不適合使用了。
總結(jié):
靜態(tài)初始化塊的優(yōu)先級最高,也就是最先執(zhí)行,并且僅在類第一次被加載時執(zhí)行;
非靜態(tài)初始化塊和構(gòu)造函數(shù)后執(zhí)行,并且在每次生成對象時執(zhí)行一次;
非靜態(tài)初始化塊的代碼會在類構(gòu)造函數(shù)之前執(zhí)行。因此若要使用,應(yīng)當(dāng)養(yǎng)成把初始化塊寫在構(gòu)造函數(shù)之前的習(xí)慣,便于調(diào)試;
靜態(tài)初始化塊既可以用于初始化靜態(tài)成員變量,也可以執(zhí)行初始化代碼;
非靜態(tài)初始化塊可以針對多個重載構(gòu)造函數(shù)進(jìn)行代碼復(fù)用。