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

在 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

 

參考閱讀:

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