Java靜態變量的初始化(static塊的本質)

本文轉載自: http://blog.csdn.net/darxin/article/details/5293427


在網上看到了下面的一段代碼:

[java] view plaincopy
  1. public class Test {  
  2.     static {  
  3.         _i = 20;  
  4.     }  
  5.     public static int _i = 10;  
  6.       
  7.     public static void main(String[] args) {  
  8.         System.out.println(_i);  
  9.     }  
  10. }  

上述代碼會打印出什麼結果來呢?10還是20?本文將以此代碼爲引子,着重討論一下靜態變量的初始化問題。

問題1:靜態變量如何初始化

Java類中可以定義一個static塊,用於靜態變量的初始化。如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i;  
  3.     static {  
  4.         _i = 10;  
  5.     }  
  6. }  

當然最常用的初始化靜態變量的操作是在聲明變量時直接進行賦值操作。如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i = 10;  
  3. }  

那麼上述兩例在本質上有什麼區別嗎?回答是沒有區別。兩例代碼編譯之後的字節碼完全一致,通過 “javap -c”查看到的字節碼如下:

public class Test extends java.lang.Object{
public static int _i;

 

public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

 

static {};
  Code:
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: return

 

}

通過字節碼還可以看出,當類的定義中不含有static塊時,編譯器會爲該類提供一個默認的static塊。當然這是在含有靜態變量初始化操作的前提下。如果靜態變量沒有初始化操作,則編譯器不會爲之提供默認的static塊。如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i;  
  3. }  

其字節碼的表現形式爲:

public class Test extends java.lang.Object{
public static int _i;

 

public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

 

}

由於靜態變量是通過賦值操作進行初始化的,因此可以通過靜態函數返回值的方式爲其初始化。如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i = init();  
  3.       
  4.     private static int init() {  
  5.         return 10;  
  6.     }  
  7. }  

其本質與下面的代碼相同:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i;  
  3.     static {  
  4.         _i = init();  
  5.     }  
  6.       
  7.     private static int init() {  
  8.         return 10;  
  9.     }  
  10. }  

問題2:JDK如何處理static塊

類定義中可以存在多個static塊嗎?回答是可以。如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i;  
  3.     static {  
  4.         _i = 10;  
  5.     }  
  6.       
  7.     public static void main(String[] args) {  
  8.     }  
  9.       
  10.     static {  
  11.         _i = 20;  
  12.     }  
  13. }  

此類編譯之後的字節碼爲:

public class Test extends java.lang.Object{
public static int _i;

 

public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

 

public static void main(java.lang.String[]);
  Code:
   0: return

 

static {};
  Code:
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: bipush 20
   7: putstatic #2; //Field _i:I
   10: return

 

}

觀察static{}部分可以看出,上例的代碼與下面的代碼效果一致:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i;  
  3.       
  4.     public static void main(String[] args) {  
  5.     }  
  6.       
  7.     static {  
  8.         _i = 10;  
  9.         _i = 20;  
  10.     }  
  11. }  

此例可以證明,不僅類定義中可以有多個static塊,而且在編譯時編譯器會將多個static塊按照代碼的前後位置重新組合成一個static塊。

問題3:如何看待靜態變量的聲明

靜態變量存放在常量池之中。如何證明呢?如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i = 10;  
  3. }  

使用“javap -c -verbose”查看其字節碼的內容如下:

public class Test extends java.lang.Object
  SourceFile: "Test.java"
  minor version: 0
  major version: 49
  Constant pool:
const #1 = Method #4.#14; //  java/lang/Object."<init>":()V
const #2 = Field #3.#15; //  Test._i:I
const #3 = class #16; //  Test
const #4 = class #17; //  java/lang/Object
const #5 = Asciz _i;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz <clinit>;
const #12 = Asciz SourceFile;
const #13 = Asciz Test.java;
const #14 = NameAndType #7:#8;//  "<init>":()V
const #15 = NameAndType #5:#6;//  _i:I
const #16 = Asciz Test;
const #17 = Asciz java/lang/Object;

 

{
public static int _i;


public Test();
  Code:
   Stack=1, Locals=1, Args_size=1
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return
  LineNumberTable: 
   line 2: 0

 

static {};
  Code:
   Stack=1, Locals=0, Args_size=0
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: return
  LineNumberTable: 
   line 3: 0

 

}

我們看到,常量池中const #2指向的就是Test._i,也就是靜態變量。靜態變量被保存到常量池中的工作原理這裏不深入討論。在此需要注意的是:

  • 靜態變量的聲明與初始化是兩個不同的操作;
  • 靜態變量的聲明在編譯時已經明確了內存的位置。

如:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i = 10;  
  3. }  

上述代碼的本質可以視爲:

[java] view plaincopy
  1. public class Test {  
  2.     // 靜態變量的聲明  
  3.     public static int _i;  
  4.   
  5.     // 靜態變量的初始化  
  6.     static {  
  7.         _i = 10;  
  8.     }  
  9. }  

由於靜態變量的聲明在編譯時已經明確,所以靜態變量的聲明與初始化在編碼順序上可以顛倒。也就是說可以先編寫初始化的代碼,再編寫聲明代碼。如:

[java] view plaincopy
  1. public class Test {  
  2.     // 靜態變量的初始化  
  3.     static {  
  4.         _i = 10;  
  5.     }  
  6.       
  7.     // 靜態變量的聲明  
  8.     public static int _i;  
  9. }  

對初始問題的解答

解答了上述三個問題,讓我們再來看看開篇提到的問題。代碼如下:

[java] view plaincopy
  1. public class Test {  
  2.     static {  
  3.         _i = 20;  
  4.     }  
  5.     public static int _i = 10;  
  6.       
  7.     public static void main(String[] args) {  
  8.         System.out.println(_i);  
  9.     }  
  10. }  

其本質可以用下面的代碼表示:

[java] view plaincopy
  1. public class Test {  
  2.     static {  
  3.         _i = 20;  
  4.     }  
  5.     public static int _i;  
  6.     static {  
  7.         _i = 10;  
  8.     }  
  9.       
  10.     public static void main(String[] args) {  
  11.         System.out.println(_i);  
  12.     }  
  13. }  

再簡化一下,可以表示爲:

[java] view plaincopy
  1. public class Test {  
  2.     public static int _i;  
  3.       
  4.     static {  
  5.         _i = 20;  
  6.         _i = 10;  
  7.     }  
  8.       
  9.     public static void main(String[] args) {  
  10.         System.out.println(_i);  
  11.     }  
  12. }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章