程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java的初始化塊、靜態初始化塊、構造函數的執行順序及用途探究,java構造函數

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

編輯:JAVA綜合教程

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


 

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

執行順序

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

  主類Main裡面也如法炮制。

1 class A { 2 static { 3 System.out.println("Static init A."); 4 } 5 6 { 7 System.out.println("Instance init A."); 8 } 9 10 A() { 11 System.out.println("Constructor A."); 12 } 13 } 14 15 class B extends A { 16 static { 17 System.out.println("Static init B."); 18 } 19 20 { 21 System.out.println("Instance init B."); 22 } 23 24 B() { 25 System.out.println("Constructor B."); 26 } 27 } 28 29 class C extends B { 30 31 static { 32 System.out.println("Static init C."); 33 } 34 35 { 36 System.out.println("Instance init C."); 37 } 38 39 C() { 40 System.out.println("Constructor C."); 41 } 42 } 43 44 public class Main { 45 46 static { 47 System.out.println("Static init Main."); 48 } 49 50 { 51 System.out.println("Instance init Main."); 52 } 53 54 public Main() { 55 System.out.println("Constructor Main."); 56 } 57 58 public static void main(String[] args) { 59 C c = new C(); 60 //B b = new B(); 61 } 62 } 測試代碼

 

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

  那麼可以看到,當程序進入了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.

 

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

  那麼如果有多個實例化對象,又會不會發生變化呢?於是在第一個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):

1 javap -c Main
1 Compiled from "Main.java" 2 public class Main { 3 public Main(); 4 Code: 5 0: aload_0 6 1: invokespecial #1 // Method java/lang/Object."<init>":()V 7 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 8 7: ldc #3 // String Instance init Main. 9 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 10 12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11 15: ldc #5 // String Constructor Main. 12 17: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13 20: return 14 15 public static void main(java.lang.String[]); 16 Code: 17 0: new #6 // class C 18 3: dup 19 4: invokespecial #7 // Method C."<init>":()V 20 7: astore_1 21 8: return 22 23 static {}; 24 Code: 25 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 26 3: ldc #8 // String Static init Main. 27 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 28 8: return 29 } 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類的反編譯結果吧:

1 javap -c C
Compiled from "Main.java" class C extends B { C(); Code: 0: aload_0 1: invokespecial #1 // Method B."<init>":()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:

 1 class C extends B {
 2 
 3     static public int a;
 4 
 5     static {
 6         a = 5;
 7         System.out.println("Static init C.");
 8     }
 9 
10 ......
11 
12 }

  main函數裡輸出這個靜態變量C.sub:

1 public static void main(String[] args) {
2     System.out.println("Value of C.sub: " + C.sub);
3 }

  則輸出結果:

Static init Main.
Static init A.
Static init B.
Static init C.
Value of C.sub: 5

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

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

 

  2.  執行初始化代碼

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

 

 非靜態初始化塊

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

 

總結: 

  

 

本文基於知識共享許可協議知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議發布,歡迎引用、轉載或演繹,但是必須保留本文的署名BlackStorm以及本文鏈接http://www.cnblogs.com/BlackStorm/p/5699965.html,且未經許可不能用於商業目的。如有疑問或授權協商請與我聯系。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved