在 Java 代碼中蔑鹦,如果要初始化一個靜態(tài)字段贮折,可以在聲明時直接賦值,也可以在靜態(tài)代碼塊中對其賦值芥炭。如果直接賦值的靜態(tài)字段被 final 所修飾,并且它的類型是基本類型或字符串時恃慧,那么該字段便會被 Java 編譯器標(biāo)記為常量值(ConstantValue)园蝠,其初始化直接由 Java 虛擬機(jī)完成。除此之外的直接賦值操作糕伐,以及所有靜態(tài)代碼塊中的代碼砰琢,則會被 Java 編譯器置于同一方法中,并把它命名為 <clinit> 。
類加載的最后一步是初始化陪汽,便是為標(biāo)記為常量值的字段賦值训唱,以及執(zhí)行 <clinit> 方法的過程。 Java 虛擬機(jī)會通過加鎖來確保 <clinit> 方法僅被執(zhí)行一次挚冤。 只有當(dāng)初始化完成之后况增,類才正式成為可執(zhí)行的狀態(tài)。
JVM規(guī)范枚舉了下述幾種情況:
- 當(dāng)虛擬機(jī)啟動時训挡,初始化用戶指定的主類
- 當(dāng)遇到調(diào)用 靜態(tài)方法 的指令時澳骤,初始化該靜態(tài)方法所在的類
- 當(dāng)遇到訪問 靜態(tài)字段 的指令時,初始化該靜態(tài)字段所在的類
- 當(dāng)遇到用以新建目標(biāo)類實例的 new 指令時澜薄,初始化 new 指令的目標(biāo)類
- 子類的初始化會觸發(fā)父類的初始化
- 如果一個接口定義了 default 方法为肮,那么直接或者間接實現(xiàn)該接口的類的初始化,會觸發(fā)該接口的初識化
- 使用反射 API 對某個類進(jìn)行反射調(diào)用時肤京,初始化這個類
- 當(dāng)初次調(diào)用 MethodHandle 實例時颊艳,初始化該 MethodHandle 指向的方法所在的類
下面的示例代碼逐個演示上述幾種情況:
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
public class Main {
static {
System.out.println("1. 當(dāng)虛擬機(jī)啟動時,初始化用戶指定的主類");
}
public static void main(String[] args) throws Throwable {
// Scenario 1,2,3,4
Scenario2.getInstance();
// Scenario 5
new Scenario5();
// Scenario 6
new Scenario6Impl();
// Scenario 7
Class<?> clazz = Class.forName("Scenario7");
Method method = clazz.getMethod("doSomething", String.class);
method.invoke(clazz.newInstance(), "somevalue");
// Scenario 8
MethodType mt = MethodType.methodType(void.class, int.class);
MethodHandle handle = MethodHandles.lookup().findStatic(Scenario8.class,"println", mt);
handle.invoke(1);
}
}
class Scenario2 {
static {
System.out.println("2. 當(dāng)遇到調(diào)用 靜態(tài)方法 的指令時忘分,初始化該靜態(tài)方法所在的類");
}
private static class Scenario3 {
static {
System.out.println("3. 當(dāng)遇到訪問 靜態(tài)字段 的指令時棋枕,初始化該靜態(tài)字段所在的類");
}
static final Scenario4 INSTANCE = new Scenario4();
}
public static Scenario4 getInstance() {
return Scenario3.INSTANCE;
}
}
class Scenario4 {
static {
System.out.println("4. 當(dāng)遇到用以新建目標(biāo)類實例的 new 指令時,初始化 new 指令的目標(biāo)類");
}
}
class Scenario5Parent {
static {
System.out.println("5. 1) 子類的初始化會觸發(fā)父類的初始化");
}
}
class Scenario5 extends Scenario5Parent {
static {
System.out.println(" 2) 子類的初始化");
}
}
interface Scenario6 {
Scenario6Field field = new Scenario6Field();
default void doSomething() {
}
}
class Scenario6Field {
static {
// 如果刪除接口中的default方法妒峦,則不會出發(fā)Scenario6Field的初始化重斑,就不會打印下面這條語句
System.out.println("6. 1) 如果一個接口定義了 default 方法,那么直接或者間接實現(xiàn)該接口的類的初始化肯骇,會觸發(fā)該接口的初識化 (如果刪除接口中的default方法窥浪,則不會出發(fā)Scenario6Field的初始化,就不會打印這條語句)");
}
}
class Scenario6Impl implements Scenario6 {
static {
System.out.println(" 2) 初始化接口實現(xiàn)類");
}
}
class Scenario7 {
static {
System.out.println("7. 使用反射 API 對某個類進(jìn)行反射調(diào)用時累盗,初始化這個類");
}
public void doSomething(String param) {
}
}
class Scenario8 {
static {
System.out.println("8. 當(dāng)初次調(diào)用 MethodHandle 實例時寒矿,初始化該 MethodHandle 指向的方法所在的類");
}
public static void println(int param) {
System.out.println(" print: " + param);
}
}
輸出結(jié)果:
1. 當(dāng)虛擬機(jī)啟動時,初始化用戶指定的主類
2. 當(dāng)遇到調(diào)用 靜態(tài)方法 的指令時若债,初始化該靜態(tài)方法所在的類
3. 當(dāng)遇到訪問 靜態(tài)字段 的指令時,初始化該靜態(tài)字段所在的類
4. 當(dāng)遇到用以新建目標(biāo)類實例的 new 指令時拆融,初始化 new 指令的目標(biāo)類
5. 1) 子類的初始化會觸發(fā)父類的初始化
2) 子類的初始化
6. 1) 如果一個接口定義了 default 方法蠢琳,那么直接或者間接實現(xiàn)該接口的類的初始化,會觸發(fā)該接口的初識化 (如果刪除接口中的default方法镜豹,則不會出發(fā)Scenario6Field的初始化傲须,就不會打印這條語句)
2) 初始化接口實現(xiàn)類
7. 使用反射 API 對某個類進(jìn)行反射調(diào)用時,初始化這個類
8. 當(dāng)初次調(diào)用 MethodHandle 實例時趟脂,初始化該 MethodHandle 指向的方法所在的類
print: 1