1. 程式人生 > >Java內部類載入問題探究

Java內部類載入問題探究

靜態內部類不持有外部類的引用

這個觀點是眾所周知的。雖然明白是因為其建構函式內沒有傳入外部類的引用。可是為什麼靜態類可以沒有傳入引用呢,靜態內部類的載入又是什麼樣的過程呢?

這幾天找到的答案,似乎都不能讓我有一種豁然開朗的感覺。於是一次新探索開始了~

╭(●`∀´●)╯

一開始,我是這樣想的:

靜態類和靜態物件,靜態變數,靜態塊等等一樣,是在類初始化時就被載入的,所以可以不需要傳入當前類的引用。 
(關於非靜態內部類,就不需要多說,一定需要外部類先例項化後才會載入。)

通過網上一個程式碼的思路,我寫出了以下demo:

import java.util.Random;

public
class OuterClass { public static long OUTER_DATE = System.currentTimeMillis(); static { System.out.println("外部類靜態塊載入時間:" + System.currentTimeMillis()); } public OuterClass() { timeElapsed(); System.out.println("外部類建構函式時間:" + System.currentTimeMillis()); } static
class InnerStaticClass { public static long INNER_STATIC_DATE = System.currentTimeMillis(); } class InnerClass { public long INNER_DATE = 0; public InnerClass() { timeElapsed(); INNER_DATE = System.currentTimeMillis(); } } public
static void main(String[] args) { OuterClass outer = new OuterClass(); System.out.println("外部類靜態變數載入時間:" + outer.OUTER_DATE); System.out.println("非靜態內部類載入時間"+outer.new InnerClass().INNER_DATE); System.out.println("靜態內部類載入時間:"+InnerStaticClass.INNER_STATIC_DATE); } //單純的為了耗時,來擴大時間差異 private void timeElapsed() { for (int i = 0; i < 10000000; i++) { int a = new Random(100).nextInt(), b = new Random(100).nextInt(); a = a + b; } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

假如我的推想沒有錯誤的話,我想應該會這樣的:

外部類常量載入時間 = 外部類靜態塊載入時間 = 靜態內部類載入時間 < 外部類建構函式時間< 非靜態內部類載入時間

Ok,我以為我離走到人生顛覆只差一個Run了 ! ✧(๑•̀ㅂ•́)و✧

結果卻是: 
這裡寫圖片描述

跪地…… ε(┬┬_┬┬)3

不過,從上面我們可以分析出來結果是:

靜態內部類和非靜態內部類一樣,都是在被呼叫時才會被載入

不信的同學可以自己試試copy以上的程式碼。隨機調換main()函式裡的方法呼叫順序,來驗證以上的規律。

後來我這麼想:

靜態內部類其實和外部類的靜態變數,靜態方法一樣,只要被呼叫了都會讓外部類的被載入。不過當只調用外部類的靜態變數,靜態方法時,是不會讓靜態內部類的被載入

嗯哼~還是來一個demo,不過是改動了點上面的東西:

import java.util.Random;

public class OuterClass {
    public static long OUTER_DATE = System.currentTimeMillis();
    public static int a = 1;
    static {
        System.out.println("外部類靜態塊載入時間:" + System.currentTimeMillis());
    }

    public OuterClass() {
        timeElapsed();
        System.out.println("外部類建構函式事件:" + System.currentTimeMillis());
    }

    static class InnerStaticClass {
        static {
            System.out.println("內部類靜態塊載入時間:" + System.currentTimeMillis());
        }
        public static double INNER_DATE = System.currentTimeMillis();

    }

    class InnerClass {
        public long INNER_DATE = 0;
        public InnerClass() {
            timeElapsed();
            INNER_DATE = System.currentTimeMillis();
        }           
    }

    public static void Hello(){System.out.println("Hello");}

    public static void main(String[] args) {
        //System.out.println("外部類常量載入時間:" + OuterClass.OUTER_DATE); 
        OuterClass.Hello();
        OuterClass outer = new OuterClass();
        System.out.println("外部類靜態變數載入時間:" + OuterClass.OUTER_DATE);
        System.out.println("外部類靜態變數載入時間:" + outer.OUTER_DATE);  
    }

    //單純的為了耗時而已
    private void timeElapsed() {
        for (int i = 0; i < 10000000; i++) {
            int a = new Random(100).nextInt(), b = new Random(100).nextInt();
            a = a + b;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

結果如下:

這裡寫圖片描述

觀點1:我說的沒錯吧~,呼叫外部類的靜態變數,靜態方法可以讓外部類得到載入,不過這裡靜態內部類沒有被載入

再看看下面的這段:

import java.util.Random;

public class OuterClass {
    public static long OUTER_DATE = System.currentTimeMillis();
    public static int a = 1;
    static {
        System.out.println("外部類靜態塊載入時間:" + System.currentTimeMillis());
    }

    public OuterClass() {
        timeElapsed();
        System.out.println("外部類建構函式事件:" + System.currentTimeMillis());
    }

    static class InnerStaticClass {
        static {
            System.out.println("內部類靜態塊載入時間:" + System.currentTimeMillis());
        }
        public static long INNER_STATIC_DATE = System.currentTimeMillis();

    }

    class InnerClass {
        public long INNER_DATE = 0;
        public InnerClass() {
            timeElapsed();
            INNER_DATE = System.currentTimeMillis();
        }           
    }

    public static void Hello(){System.out.println("Hello");}

    public static void main(String[] args) {
        System.out.println("內部類靜態變數載入時間:" + InnerStaticClass.INNER_STATIC_DATE );
        System.out.println("外部類靜態變數載入時間:" + OuterClass.OUTER_DATE );
    }

    //單純的為了耗時而已
    private void timeElapsed() {
        for (int i = 0; i < 10000000; i++) {
            int a = new Random(100).nextInt(), b = new Random(100).nextInt();
            a = a + b;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

結果: 
這裡寫圖片描述 
觀點2:可以看出,我們其實載入靜態內部類的時候,其實還會先載入外部類,才載入靜態內部類

由觀點1與觀點2聯立,以上論點得證!

好,迴歸主題,為什麼靜態內部類可以不傳入引用呢?

因為其本質就是針對外部類的內部類,而不是物件的內部類,不必用this來呼叫。 
於是接下來打算用口遁入手來說服大家: 
(“▔□▔)/(“▔□▔)/(“▔□▔)/

首先,從靜態的概念出發理解,靜態修飾過後的一切物件都只與類相關,不與物件引用相關

As we known,靜態變數,靜態方法,靜態塊等都是類級別的屬性,而不是單純的物件屬性。他們在類第一次被使用時被載入(記住,是一次使用,不一定是例項化)。我們可以簡單得用 類名.變數 或者 類名.方法來呼叫它們。與呼叫沒有被static 修飾過變數和方法不同的是:一般變數和方法是用當前物件的引用(即this)來呼叫的,靜態的方法和變數則不需要。從一個角度上來說,它們是共享給所有物件的,不是一個角度私有。這點上,靜態內部類也是一樣的。

有人說,靜態內部類可以當作一個頂級類來看,不過我在《Java的synthetic修飾詞》一文中也提到過,所有的內部類都是頂級類。所以我覺得以覺得“靜態內部類可以當作一個頂級類來看”這一說法,並不恰當。

其中更深層的東西,又涉及到JVM虛擬機器部分,我點一下,不明白的可以先看看《初探Java虛擬機器及其載入過程》

針對非靜態內部類,堆中的每一個外部類的物件都各自持有一份非靜態內部類在方法區符號引用。而針對靜態內部類,堆中的所有的外部類物件都共同持有一份靜態內部類在方法區的符號引用。

以上純屬個人推測,如有指正,支付寶10元立轉!!!! \( ̄▽ ̄)/

靜態內部類的載入過程:

靜態內部類的載入不需要依附外部類,在使用時才載入。不過在載入靜態內部類的過程中也會載入外部類。以上花了很多功夫來說明了

總結

耗時兩天,得以做完,期間查了很多資料,想了很多彎路。最後得以完成。也是謝天謝地。還是很開心的~,XD,這段時間先不研究這些編譯載入的方向了。感覺如果沒人給予指點的話,很容易思想走差,走火入魔,恰恰懂得人也不多…..