Java的初始化塊、靜態初始化塊、構造函數的執行順序及用途探究

Java與C++有一個不同之處在于,Java不但有構造函數,還有一個”初始化塊“(Initialization Block)的概念。下面探究一下它的執行順序與可能的用途。

執行順序

首先定義A, B, C三個類用作測試,其中B繼承了A,C又繼承了B,并分別給它們加上靜態初始化塊、非靜態初始化塊和構造函數,里面都是一句簡單的輸出。

主類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();

}

}

測試代碼

當然這里不使用內部類,因為內部類不能使用靜態的定義;而用靜態內部類就失去了一般性。

那么可以看到,當程序進入了main函數,并創建了一個類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.

觀察上面的輸出,可以觀察到兩個有趣的現象:

Main類是肯定沒有被實例化過的,但是由于執行main入口函數用到了Main類,于是static初始化塊也被執行了;

所有的靜態初始化塊都優先執行,其次才是非靜態的初始化塊和構造函數,它們的執行順序是:

父類的靜態初始化塊

子類的靜態初始化塊

父類的初始化塊

父類的構造函數

子類的初始化塊

子類的構造函數

那么如果有多個實例化對象,又會不會發生變化呢?于是在第一個C類的對象后面,再實例化一個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.

可以發現這輸出跟前面的基本長得一樣對吧?只是在后面多了4行,那是新的B類對象實例化時產生的信息,同樣也是父類A的初始化塊和構造函數先執行,再輪到子類B的初始化塊和構造函數執行;同時還發現,靜態初始化塊的輸出只出現了一次,也就是說每個類的靜態初始化塊都只在第一次實例化該類對象時執行一次。

無論如何,初始化塊和構造函數總在一起執行是件有趣的事情,讓我們反編譯一下看看吧!

查看生成目錄發現已經生成了4個.class文件,分別是A.class, B.class, C.class, Main.class,先看看Main.class的結構(這里重新注釋了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的反編譯結果

可以看到整個Main類被分成三個部分,static {}部分很顯然,就是我們的static初始化塊,在里面調用了println并輸出了String“Static init Main.”;而main入口函數也很清晰,首先新實例化了一個類C的對象,然后調用了類C的構造函數,最后返回;而上面public Main();的部分就很有意思了,這是類Main的構造函數,但我們看到里面調用了兩次println,分別輸出了String“Instance init Main.”和String“Constructor Main.”。難道初始化塊和構造函數被合并到一起了?

我們再看看C類的反編譯結果吧:

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的反編譯結果

靜態初始化塊仍然單獨分出一部分,輸出了我們的調試語句。而另一部分,仍然還是類C的構造函數C();,可以看到它先調用了父類B的構造函數,接著輸出了我們初始化塊中的語句,然后才輸出我們寫在構造函數中的語句,最后返回。多次試驗也都是如此。于是我們能夠推斷:初始化塊的代碼是被加入到子類構造函數的前面,父類初始化的后面了。

可能的用途:

既然執行順序和大概原理都摸清了,那么就要探討一下初始化塊的可能的用途。

靜態初始化塊

1. ?用于初始化靜態成員變量

比如給類C增加一個靜態成員變量sub,我們在static塊里面給它賦值為5:

class C extends B {

static public int a;

static {

a = 5;

System.out.println("Static init C.");

}

......

}

main函數里輸出這個靜態變量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:?

符合類被第一次加載時執行靜態初始化塊的結論,且C.sub被正確賦值為5并輸出了出來。

但是乍一看似乎沒有什么用,因為靜態成員變量在定義時就可以順便賦值了。因此在賦值方面有點雞肋。

2. ?執行初始化代碼

比如可以記錄第一次訪問類的日志,或方便單例模式的初始化等。對于單例模式,可以先用static塊初始化一些可能還被其他類訪問的基礎參數,等到真正需要加載大量資源的時候(getInstance)再構造單體,在構造函數中加載資源。

非靜態初始化塊

這個就沒什么好說的了,基本跟構造函數一個功能,但比構造函數先執行。最常見的用法應該還是代碼復用,即多個重載構造函數都有若干段相同的代碼,那么可以把這些重復的代碼拉出來放到初始化塊中,但仍然要注意它的執行順序,對順序有嚴格要求的初始化代碼就不適合使用了。

總結:

靜態初始化塊的優先級最高,也就是最先執行,并且僅在類第一次被加載時執行;

非靜態初始化塊和構造函數后執行,并且在每次生成對象時執行一次;

非靜態初始化塊的代碼會在類構造函數之前執行。因此若要使用,應當養成把初始化塊寫在構造函數之前的習慣,便于調試;

靜態初始化塊既可以用于初始化靜態成員變量,也可以執行初始化代碼

非靜態初始化塊可以針對多個重載構造函數進行代碼復用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,729評論 18 399
  • Java的初始化可以分為兩個部分:(a)類的初始化(b)對象的創建(a)類的初始化 **一、概念介紹: ** 一個...
    夢工廠閱讀 4,201評論 1 24
  • 父類 static 塊 1 執行 父類 靜態成員staticSam1初始化 父類 靜態成員staticSam2初始...
    YCix閱讀 1,333評論 0 0
  • Win7下如何打開DOS控制臺? a:開始--所有程序--附件--命令提示符 b:開始--搜索程序和文件--cmd...
    逍遙嘆6閱讀 1,608評論 4 12
  • 比如說我,每次看到那些長篇大論如何減肥如何飲食如何運動如何請教練的人我就嗤之以鼻,??請原諒我用詞不當,大家都是為了...
    ysa悟空閱讀 293評論 0 1