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


<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>方法:

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

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

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

  • 案例解析
  1. 關(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ò)誤

  1. 關(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)用 Classjava.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'}
    }

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容