jvm基礎第三節(jié): <clinit> 與 <init> 方法


<clinit>方法

先理解 類初始化階段 的含義: 該階段負責為類變量賦予正確的初始值, 是一個類或接口被首次使用前的最后一項工作

  • <clinit>方法 的執(zhí)行時期: 類初始化階段(該方法只能被jvm調用, 專門承擔類變量的初始化工作)

  • <clinit>方法 的內容: 所有的類變量初始化語句和類型的靜態(tài)初始化器

  • 類的初始化時機: 即在java代碼中首次主動使用的時候, 包含以下情形:
    - (首次)創(chuàng)建某個類的新實例時--new, 反射, 克隆 或 反序列化;
    - (首次)調用某個類的靜態(tài)方法時;
    - (首次)使用某個類或接口的靜態(tài)字段或對該字段(final 字段除外)賦值時;
    - (首次)調用java的某些反射方法時;
    - (首次)初始化某個類的子類時;
    - (首次)在虛擬機啟動時某個含有 main() 方法的那個啟動類

注意: 并非所有的類都會擁有一個<clinit>方法, 滿足下列條件之一的類不會擁有<clinit>方法:

  1. 該類既沒有聲明任何類變量,也沒有靜態(tài)初始化語句;

  2. 該類聲明了類變量,但沒有明確使用類變量初始化語句或靜態(tài)初始化語句初始化;

  3. 該類僅包含靜態(tài) final 變量的類變量初始化語句,并且類變量初始化語句是編譯時常量表達式;

  • 案例解析
  1. 關于編譯錯誤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的編譯錯誤

  1. 關于靜態(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. 調用 Classjava.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'}
    }

}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容