<clinit>方法
先理解 類初始化階段 的含義: 該階段負(fù)責(zé)為類變量賦予正確的初始值, 是一個(gè)類或接口被首次使用前的最后一項(xiàng)工作
<clinit>方法 的執(zhí)行時(shí)期:
類初始化階段(該方法只能被jvm調(diào)用, 專門承擔(dān)類變量的初始化工作)<clinit>方法 的內(nèi)容: 所有的類變量初始化語句和類型的靜態(tài)初始化器
類的初始化時(shí)機(jī): 即在java代碼中首次主動(dòng)使用的時(shí)候, 包含以下情形:
- (首次)創(chuàng)建某個(gè)類的新實(shí)例時(shí)--new, 反射, 克隆 或 反序列化;
- (首次)調(diào)用某個(gè)類的靜態(tài)方法時(shí);
- (首次)使用某個(gè)類或接口的靜態(tài)字段或?qū)υ撟侄?final 字段除外)賦值時(shí);
- (首次)調(diào)用java的某些反射方法時(shí);
- (首次)初始化某個(gè)類的子類時(shí);
- (首次)在虛擬機(jī)啟動(dòng)時(shí)某個(gè)含有 main() 方法的那個(gè)啟動(dòng)類
注意: 并非所有的類都會(huì)擁有一個(gè)<clinit>方法, 滿足下列條件之一的類不會(huì)擁有<clinit>方法:
該類既沒有聲明任何類變量,也沒有靜態(tài)初始化語句;
該類聲明了類變量,但沒有明確使用類變量初始化語句或靜態(tài)初始化語句初始化;
該類僅包含靜態(tài) final 變量的類變量初始化語句,并且類變量初始化語句是編譯時(shí)常量表達(dá)式;
- 案例解析
- 關(guān)于編譯錯(cuò)誤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); // 調(diào)用類變量test, 未出現(xiàn)編譯錯(cuò)誤
System.out.println(s); // 調(diào)用類變量s, 出現(xiàn)編譯錯(cuò)誤illegal forward reference
}
static String s = "string";
}
結(jié)論:
在static語句塊中使用到靜態(tài)變量時(shí)一定要將該靜態(tài)變量的聲明語句放在static語句塊的前面, 否則會(huì)發(fā)生illegal forward references的編譯錯(cuò)誤
- 關(guān)于靜態(tài)常量(static final類型)的賦值時(shí)機(jī)所引起的問題:
// 對(duì)比下面兩段代碼的輸出結(jié)果
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); // 輸出結(jié)果為: 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); // 輸出結(jié)果為: null
}
}
分析: 上述代碼段1中由于name被賦予字符串字面量"string_name", 故在name聲明時(shí)其值就是"string_name"; 而代碼段2中由于使用new String方式為name賦值, 導(dǎo)致name在聲明時(shí)未被初始化(默認(rèn)為null), 直到static語句塊執(zhí)行后才會(huì)被初始化為"string_name", 而static語句塊執(zhí)行期間調(diào)用類的構(gòu)造方法, 構(gòu)造方法中使用了name, 注意此時(shí)name并未被賦值,因此testName為null.
結(jié)論: 要保證靜態(tài)常量在使用前被賦予值, 否則會(huì)出現(xiàn)意想不到的情況.
<init>方法:
<init>方法 的執(zhí)行時(shí)期: 對(duì)象的初始化階段
實(shí)例化一個(gè)類的四種途徑:
1. 調(diào)用new操作符
2. 調(diào)用Class或java.lang.reflect.Constructor對(duì)象的newInstance()方法
3. 調(diào)用任何現(xiàn)有對(duì)象的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'}
}
}