java 類初始化探究

不面試不知道自己有多菜,遇到一個類初始化的面試題,寫不出來只能亡羊補牢了

class Simple {
    private static Simple simple = new Simple();
    private ChildObject childObject = new ChildObject();

    public Simple() {
        System.out.println("Simple");
    }

    public static void main(String[] args) {
        System.out.println("main Simple");
    }

    static {
        System.out.println("static Simple");
    }
}

public class ParentObject {
    public ParentObject() {
        System.out.println("父類構造函數");
    }
}

public class ChildObject extends ParentObject {
    public ChildObject() {
        System.out.println("子類構造函數");
    }

    static {
        System.out.println("子類靜態代碼塊");
    }
}

題目是寫出輸出,你可以試着寫一下,答案我就放在最後了

靜態變量,靜態塊,普通代碼塊,構造函數等在類初始化的執行順序如下:

父類靜態變量
父類靜態代碼塊
子類靜態變量
子類靜態代碼塊
父類實例變量
父類代碼塊
父類構造函數
子類實例變量
子類代碼塊
子類構造函數

* 如果是第二次初始化則沒有靜態變量和靜態代碼塊的初始化

 

 

上題中還存在一個難點就是類中定義了一個靜態的自己的實例。

class Simple {
    private static Simple simple = new Simple();
    ...
}

對於初始化階段,虛擬機嚴格規範了有且只有5種情況下,必須對類進行初始化(只有主動去使用類纔會初始化類):

  1. 當遇到 new 、 getstatic、putstatic或invokestatic 這4條直接碼指令時,比如 new 一個類,讀取一個靜態字段(未被 final 修飾)、或調用一個類的靜態方法時。
    • 當jvm執行new指令時會初始化類。即當程序創建一個類的實例對象。
    • 當jvm執行getstatic指令時會初始化類。即程序訪問類的靜態變量(不是靜態常量,常量會被加載到運行時常量池)。
    • 當jvm執行putstatic指令時會初始化類。即程序給類的靜態變量賦值。
    • 當jvm執行invokestatic指令時會初始化類。即程序調用類的靜態方法。
  2. 使用 java.lang.reflect 包的方法對類進行反射調用時如Class.forname("..."),newInstance()等等。 ,如果類沒初始化,需要觸發其初始化。
  3. 初始化一個類,如果其父類還未初始化,則先觸發該父類的初始化。
  4. 當虛擬機啓動時,用戶需要定義一個要執行的主類 (包含 main 方法的那個類),虛擬機會先初始化這個類。
  5. MethodHandle和VarHandle可以看作是輕量級的反射調用機制,而要想使用這2個調用, 就必須先使用findStaticVarHandle來初始化要調用的類。

 

本題就涉及到第4條。執行過程中會直接按靜態變量,靜態代碼塊的順序初始化Simple類的信息。初始化靜態變量時就會創建一個Simple的實例。這一步就相當於第二次調用Simple初始化的過程。僅會按實例變量,代碼塊,構造函數這個順序初始化對應的Simple實例。結束初始化實例操作之後會繼續靜態變量,靜態代碼塊的初始化任務。完成之後執行main函數。

問題的答案爲

子類靜態代碼塊
父類構造函數
子類構造函數
Simple
static Simple
main Simple

 

 

補充

class PrintObject {
    public PrintObject() {
    }

    public PrintObject(String msg) {
        System.out.println(msg);
    }
}

class ParentObject {
    private static PrintObject parentObject_ = new PrintObject("父類靜態變量");
    private PrintObject parentObject = new PrintObject("父類實例變量");
    static {
        new PrintObject("父類靜態代碼塊");
    }
    {
        new PrintObject("父類代碼塊");
    }
    public ParentObject() {
        System.out.println("父類構造函數");
    }
    public ParentObject(String msg) {
        System.out.println("父類構造函數");
    }
}

class ChildObject extends ParentObject {
    private static PrintObject parentObject_ = new PrintObject("子類靜態變量");
    private PrintObject parentObject = new PrintObject("子類實例變量");
    static {
        System.out.println("子類靜態代碼塊");
    }
    {
        new PrintObject("子類代碼塊");
    }
    public ChildObject() {
        System.out.println("子類構造函數");
    }
}

class Simple {
    private static Simple simple = new Simple();
    private ChildObject childObject = new ChildObject();

    static {
        System.out.println("static Simple");
    }

    {
        System.out.println("block Simple");
    }

    public Simple() {
        System.out.println("Simple");
    }

    public static void main(String[] args) {
        System.out.println("main Simple");
        new ChildObject();
    }

}

輸出

父類靜態變量
父類靜態代碼塊
子類靜態變量
子類靜態代碼塊
父類實例變量
父類代碼塊
父類構造函數
子類實例變量
子類代碼塊
子類構造函數
block Simple
Simple
static Simple
main Simple
父類實例變量
父類代碼塊
父類構造函數
子類實例變量
子類代碼塊
子類構造函數

.java文件被編譯成.class文件後,普通代碼塊的內容被放進了構造函數裏的開頭,如打開ParentObject.class文件中的內容爲

class ParentObject {
    private static PrintObject parentObject_ = new PrintObject("父類靜態變量");
    private PrintObject parentObject = new PrintObject("父類實例變量");

    public ParentObject() {
        new PrintObject("父類代碼塊");
        System.out.println("父類構造函數");
    }

    public ParentObject(String msg) {
        new PrintObject("父類代碼塊");
        System.out.println("父類構造函數");
    }

    static {
        new PrintObject("父類靜態代碼塊");
    }
}

 

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