1. 程式人生 > >static塊的本質

static塊的本質

效果 con article csdn 組合 證明 合成 函數 rgs

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

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

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

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

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

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

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

[java] view plain copy
  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 plain copy
  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 plain copy
  1. public class Test {
  2. public static int _i = init();
  3. private static int init() {
  4. return 10;
  5. }
  6. }

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

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

問題2:JDK如何處理static塊

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

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

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

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 plain copy
  1. public class Test {
  2. public static int _i;
  3. public static void main(String[] args) {
  4. }
  5. static {
  6. _i = 10;
  7. _i = 20;
  8. }
  9. }

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

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

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

[java] view plain copy
  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 plain copy
  1. public class Test {
  2. public static int _i = 10;
  3. }

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

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

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

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

對初始問題的解答

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

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

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

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

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

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

至此,代碼已經明確告訴我們打印結果是什麽了!

static塊的本質