<clinit>方法
先理解 類初始化階段 的含義: 該階段負責為類變量賦予正確的初始值, 是一個類或接口被首次使用前的最后一項工作
<clinit>方法 的執(zhí)行時期:
類初始化階段(該方法只能被jvm調用, 專門承擔類變量的初始化工作)<clinit>方法 的內容: 所有的類變量初始化語句和類型的靜態(tài)初始化器
類的初始化時機: 即在java代碼中首次主動使用的時候, 包含以下情形:
- (首次)創(chuàng)建某個類的新實例時--new, 反射, 克隆 或 反序列化;
- (首次)調用某個類的靜態(tài)方法時;
- (首次)使用某個類或接口的靜態(tài)字段或對該字段(final 字段除外)賦值時;
- (首次)調用java的某些反射方法時;
- (首次)初始化某個類的子類時;
- (首次)在虛擬機啟動時某個含有 main() 方法的那個啟動類
注意: 并非所有的類都會擁有一個<clinit>方法, 滿足下列條件之一的類不會擁有<clinit>方法:
該類既沒有聲明任何類變量,也沒有靜態(tài)初始化語句;
該類聲明了類變量,但沒有明確使用類變量初始化語句或靜態(tài)初始化語句初始化;
該類僅包含靜態(tài) final 變量的類變量初始化語句,并且類變量初始化語句是編譯時常量表達式;
- 案例解析
- 關于編譯錯誤illegal forward reference(違法向前引用):
package com.jvm.exercises;
/**
* @author dimdark
*/
public class ClinitAndInitTest {
static ClinitAndInitTest test = new ClinitAndInitTest();
// 靜態(tài)語句塊
static {
System.out.println("static statements block");
// 注意 test 與 s 的聲明位置
System.out.println(test); // 調用類變量test, 未出現(xiàn)編譯錯誤
System.out.println(s); // 調用類變量s, 出現(xiàn)編譯錯誤illegal forward reference
}
static String s = "string";
}
結論:
在static語句塊中使用到靜態(tài)變量時一定要將該靜態(tài)變量的聲明語句放在static語句塊的前面, 否則會發(fā)生illegal forward references的編譯錯誤
- 關于靜態(tài)常量(static final類型)的賦值時機所引起的問題:
// 對比下面兩段代碼的輸出結果
package com.jvm.exercises;
/**
* @author dimdark
*/
public class ClinitTestFive {
private static ClinitTestFive test;
static {
test = new ClinitTestFive();
}
private static final String name = "string_name";
private String testName;
private ClinitTestFive() {
testName = name;
}
public static void main(String[] args) {
System.out.println(test.testName); // 輸出結果為: string_name
}
}
package com.jvm.exercises;
/**
* @author dimdark
*/
public class ClinitTestFive {
private static ClinitTestFive test;
static {
test = new ClinitTestFive();
}
private static final String name = new String("string_name");
private String testName;
private ClinitTestFive() {
testName = name;
}
public static void main(String[] args) {
System.out.println(test.testName); // 輸出結果為: null
}
}
分析: 上述代碼段1中由于name被賦予字符串字面量"string_name", 故在name聲明時其值就是"string_name"; 而代碼段2中由于使用new String方式為name賦值, 導致name在聲明時未被初始化(默認為null), 直到static語句塊執(zhí)行后才會被初始化為"string_name", 而static語句塊執(zhí)行期間調用類的構造方法, 構造方法中使用了name, 注意此時name并未被賦值,因此testName為null.
結論: 要保證靜態(tài)常量在使用前被賦予值, 否則會出現(xiàn)意想不到的情況.
<init>方法:
<init>方法 的執(zhí)行時期: 對象的初始化階段
實例化一個類的四種途徑:
1. 調用new操作符
2. 調用Class或java.lang.reflect.Constructor對象的newInstance()方法
3. 調用任何現(xiàn)有對象的clone()方法
4. 通過java.io.ObjectInputStream類的getObject()方法反序列化小案例:
package com.jvm.exercises;
/**
* @author dimdark
*/
public class InitTest {
private int code = 0;
InitTest() {
code = 1;
name = "init_name";
}
private String name = "name";
@Override
public String toString() {
return "InitTest{" +
"code=" + code +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) {
System.out.println(new InitTest()); // InitTest{code=1, name='init_name'}
}
}