探討Java類中成員變數的初始化方式

iteye_401發表於2013-08-14

在 Java 裡定義一個類的時候,很多時候我們需要提供成員變數,成員變數專業叫法是 Memeber Variable 或者乾脆的叫作 Field. 根據是否使用 static 關鍵字修飾,可以將 Field 分為兩種:

  1. static field:也稱作 class variable,這種 filed 屬於 class,並不屬於單個 instance,所有該 class 的 intance 共享記憶體中的同一份 class field。
  2. non-static field:也稱作 instance variable,它屬於每一個具體的 instance,class 的每一個 instance 獨享一份 non-static field。

接下來進入本文的主題:java 中 field 的初始化方式。

從初始化程式碼所在的位置看,可以粗略的分為三種:

  1. 在宣告 field 的地方進行初始化。
  2. 在類的構造方法(constructor) 裡對 field 進行初始化
  3. 在初始化塊(initialization block) 中對已宣告的 field 進行初始化

第一種方式主要用於簡單的賦值,使用這種方式的前提是作為初始化變數的值是已知的並且通常可以使用單行的賦值語句完成(例外?參見 Double Brace Initialization)。

 

public class Foo {
    // class variable initializer
    private static Logger logger = LoggerFactory.getLogger(Foo.class);

    // instance variable initializer
    private List<String> fooList = new ArrayList<String>();
}

 

對於複雜的初始化語句,如包含異常處理(try-catch)、使用迴圈結構初始化等,則需要考慮另外兩種初始化方式:constructor 和 initialization block。其中 initialization block 根據是否由 static 關鍵字修飾,又可分為 static(class) initialization block 和 instance(object) initialization block,前一種只能初始化 class variable,用它進行 instance variable 的初始化會導致編譯錯誤。

 

構造方法(constructor)可以用於初始化 instance variable。除此之外,少數情況下,instance variable 的初始化需要考慮使用 instance initialization block 完成。例如,在匿名類中的初始化(因匿名類無構造方法),或者類中包含了多個 constructor,而它們有公共的一些複雜初始化操作,此時可以考慮將這些操作提取到 instance initialization block 裡。除了這兩種情況,在你的程式碼中應該儘量少使用 instance initialization block。

 

static initialization block 用於處理帶有 class variable 的初始化操作。

 

public class BarClass {

	private static Properties propTable;

	static {
		try {
			propTable.load(new FileInputStream("/data/user.prop"));
		} catch (Exception e) {
			propTable.put("user", System.getProperty("user"));
			propTable.put("password", System.getProperty("password"));
		}
	}
}

 

static initialization block  的另一個功能與執行緒安全(thread safe)相關。JVM 保證使用同一個 ClassLoader 載入的類中的 static initialization block 只被執行一次,因而它是執行緒安全的。也正因為這一點,很多時候我們可以利用 static initialization block 執行一些初始化(write)操作,而無需對該 block 使用任何同步機制。

 

最後來看一下初始化程式碼的執行順序問題。在此之前,先看下面這段程式碼,它可以完整執行,請試著分析一下最後的輸出是什麼。

 

/**
 * @author wxl24life
 *
 */
public class ClassInitializerOrderTest{
	public static void main(String[] args) {
		B a = new B();
	}
}

class A {
	static int a = initA();
	static int initA() {
		System.out.println("call 1");
		return 0;
	}
	
	{
		System.out.println("call 5");
	}
	{
		System.out.println("call 6");
	}
	
	static {
		System.out.println("call 2");
	}
	static {
		System.out.println("call 3");
	}
	
	static int b = initB();
	static int initB() {
		System.out.println("call 4");
		return 0;
	}

	A() {
		System.out.println("call 7");
	}
}

class B extends A {
	{
		System.out.println("call 8");
	}

	String str = initStr();
	String initStr() {
		System.out.println("call 9");
		return "call 8";
	}
	
	static {
		System.out.println("call 10");
	}

	B() {
		super();
		System.out.println("call 12");
	}

	{
		System.out.println("call 11");
	}
}

 

用幾句話概括下初始化順序規則(假設呼叫方式類似於上面程式碼,即使用 new 操作符 ):

  1. static 先於 non-static, non-static 先於 constructor。這裡的 static 統指 static field 和 static initialization block 兩種初始化方式,non-static 同上。
  2. static 初始化程式碼按照在原始碼中定義的順序從上往下以此執行,non-static 同上。
  3. 存在繼承關係時,優先執行基類中的初始化語句。

執行順序測試程式碼的輸出結果:

call 1
call 2
call 3
call 4
call 10
call 5
call 6
call 7
call 8
call 9
call 11
call 12

 

參考閱讀:

相關文章