Java中變量的初始化順序

Java中變量的初始化順序

在寫一個通用的報警模塊時,遇到一個有意思的問題,在調用靜態方法時,發現靜態方法內部對靜態變量引用時,竟然拋出了npe,彷彿是由於這個靜態變量的初始化在靜態方法被調用時,尚未觸發,從而致使這個問題,所以今天專門來學習下靜態成員的初始化順序,以及上面這個問題致使的緣由java

I. 初始化順序

類的初始化順序ide

靜態變量, 靜態代碼快 -》 實例變量(屬性,實例代碼塊,構造方法)post

繼承關係初始化順序學習

父類靜態成員,靜態代碼塊 -》 子類靜態成員,靜態代碼塊 -》 父類實例變量(屬性,實例代碼塊,構造方法)-》子類實例變量(屬性,實例代碼塊,構造方法)測試

II. 靜態變量初始化順序

類初始化時,會優先初始化靜態成員,那麼一個類中有多個靜態成員時,如何處理的?idea

下面是一個使用靜態成員,靜態代碼塊,靜態方法的測試類,那麼下面的輸出應該是怎樣的呢?debug

public class StaticTest {
    static class A {
        public A(int i) {
            System.out.println("a init! " + i);
        }
    }
    static class B {
        public B(int i) {
            System.out.println("b init! " + i);
        }
    }

    private static A a1 = new A(1);
    private static B b1;

    private static int num;

    private static B b2 = new B(2);
    private static A a2 = genA(2);

    static {
        b1 = new B(1);
    }

    private static A genA(int i) {
        System.out.println("gen A: " + i);
        return new A(i);
    }

    private static B genB(int i) {
        System.out.println("gen B: " + i);
        return new B(i);
    }

    public static void doSome() {
        System.out.println("static function doSome called! a3!=null : " + (a3 != null) + " | num > 0 : " + num);
    }

    private static A a3;
    private static B b3;
    
    static {
        System.out.println("num : " + num);
        num = 10;
        a3 = genA(3);
        b3 = genB(3);
    }

    public static void main(String[] args) {
        doSome();
    }
}

輸出以下code

a init! 1
b init! 2
gen A: 2
a init! 2
b init! 1
num : 0
gen A: 3
a init! 3
gen B: 3
b init! 3
static function doSome called! a3!=null : true | num > 0 : 10

從實際的輸出結果來看:blog

  • 初始化的順序比較清晰了,壓根就是根據初始化代碼的前後順序來的,
  • 且在調用靜態方法時,靜態方法內部的靜態成員已經被初始化

那麼問題來了,若是在某個靜態成員初始化的時候拋出了異常,會怎樣?繼承

那麼稍稍改一下上面的代碼,加一個主動拋異常的case

public class StaticTest {

    static class A {
        public A(int i) {
            System.out.println("a init! " + i);
        }
    }

    static class B {
        public B(int i) {
            System.out.println("b init! " + i);
        }
    }


    private static A a1 = new A(1);
    private static B b1;

    private static int num;

    private static B b2 = new B(2);
    private static A a2 = genA(2);

    static {
        b1 = new B(1);
    }

    private static A genA(int i) {
        System.out.println("gen A: " + i);
        return new A(i);
    }

    private static B genB(int i) {
        System.out.println("gen B: " + i);
        return new B(i);
    }

    private static A aError = genError();
    private static A genError() {
        System.out.println("gen error!");
        throw new RuntimeException();
//        return new A(10);
    }

    public static void doSome() {
        System.out.println("static function doSome called! a3!=null : " + (a3 != null) + " | num > 0 : " + num);
    }

    private static A a3;
    private static B b3;

    static {
        System.out.println("num : " + num);
        num = 10;
        a3 = genA(3);
        b3 = genB(3);
    }




    public static void main(String[] args) {
        doSome();
    }
}

此時輸出:

a init! 1
b init! 2
gen A: 2
a init! 2
b init! 1
gen error!
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException

也就是說,初始化異常以後的代碼將不會在繼續執行

那麼第二個問題來了,前面說到哪一個問題是什麼狀況

最開始說到,在調用類的靜態方法時,發現本該被初始化的靜態成員,依然是null,從上面的分析來講,惟一的可能就是在成員變量初始化的過程當中,出現了異常

那麼,就有另外一個問題了,初始化就報錯了,這個類的靜態方法還能被調用執行麼(加入這個靜態方法不依賴內部的靜態成員)?

將前面的 genA()方法的private去掉,改爲默認的訪問範圍,而後下面給出一個演示:

post.gif

經過這個演示,也挺有意思的,第一次訪問,會拋出一個初始化異常;可是再調用一次,結果發現竟然正常執行了;可是調用public方法時,每次都是拋異常

致使這個問題的緣由,還有待考究,可是前面這個問題的答案,估摸着和下面差很少了(可是不敢肯定,有待大神指點)

  • 理論上類初始化失敗,應該就不容許被調用了
  • 可是某些狀況下,能夠繞過這個限制

III. 成員變量的初始化

測試case也比較簡單,把前面的代碼中的static去掉便可, 輸出

a init! 1
b init! 2
gen A: 2
a init! 2
b init! 1
num : 0
gen A: 3
a init! 3
gen B: 3
b init! 3
static function doSome called! a3!=null : true | num > 0 : 10

依然是根據初始化代碼的前後順序進行的

固然若是出現異常的狀況,和前面的結果相似,再也不贅述

IV. 小結

1. 初始化順序

類的初始化順序

靜態變量, 靜態代碼快 -》 實例變量(屬性,實例代碼塊,構造方法)

繼承關係初始化順序

父類靜態成員,靜態代碼塊 -》 子類靜態成員,靜態代碼塊 -》 父類實例變量(屬性,實例代碼塊,構造方法)-》子類實例變量(屬性,實例代碼塊,構造方法)

相同等級的初始化的前後順序,是直接依賴代碼中初始化的前後順序

2. 初始化異常時

理論上,類初始化中拋出了異常,那麼這個類將沒法被classLoader正確的加載,所以也沒法有效的使用這個類

可是不排除某些狀況下,依然強行的使用了這個類(如上面gif圖中的演示),這個原理還不太清晰,也有多是idea的debug功能有什麼黑科技?

注意

所以,請格外注意,在初始化代碼中,請確保不會有拋出異常,若是沒法把控,不妨新建一個init()方法來實現初始化各類狀態,而後在代碼中主動調用好了

V. 其餘

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正

掃描關注,java分享

QrCode

相關文章
相關標籤/搜索