天天看點

java 中的靜态變量,靜态代碼塊,動态代碼塊,構造方法執行順序的深入探究

要想完全弄懂這個執行順序,需要我們先了解幾個概念。

首先是類加載與對象的構造,類加載就是在第一次調用這個類的時候jvm虛拟機會通過類加載器在一個叫做方法區的邏輯記憶體中将所要用到的類的資訊存放在裡邊,其中方法區有一個靜态區,存放的是類中的靜态(類變量),而對象構造則隻是在堆中開辟一個記憶體空間将執行個體化的對象存放在裡邊,在生命周期中要遠遠小于類。

第二是java中關于靜态代碼塊與動态代碼塊的注釋,靜态代碼塊隻有在類加載時僅且執行一次,而動态代碼塊是在每次構造對象時執行一次,然後才是構造方法,也就有了一個順序,靜态代碼塊>動态代碼塊>構造方法,而靜态變量與靜态代碼塊的優先級是一個級别的,這樣看來好像問題解決了,但其實問題并不是這麼簡單。

接下來還是找一個有說服力的例子,阿裡巴巴的一道面試題。

//package com.basics.day09;

public class Test {
    public static int k = 0;
    public static Test t1 = new Test("t1");
    public static Test t2 = new Test("t2");
    public static int i = print("i");
    public static int n = 99;
    private int a = 0;
    public int j = print("j");
    
    {
        print("構造塊");
    }
 
    static {
        print("靜态塊");
    }
 
    public Test(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "     n=" + n);
        ++i;
        ++n;
    }
 
    public static int print(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "     n=" + n);
        ++n;
        return ++i;
    }
 
    public static void main(String args[]) {
        Test t = new Test("init");
    }

}
           

運作結果:

1:j    i=0     n=0
2:構造塊    i=1     n=1
3:t1    i=2     n=2
4:j    i=3     n=3
5:構造塊    i=4     n=4
6:t2    i=5     n=5
7:i    i=6     n=6
8:靜态塊    i=7     n=99
9:j    i=8     n=100
10:構造塊    i=9     n=101
11:init    i=10     n=102
           

這樣的結果是不是與我們所想的不一樣,按照上邊的那個順序,應該是先靜态變量,然後是靜态代碼塊,然後是動态代碼塊,最後才是構造函數,可是上邊的結果,很明顯是先執行的非靜态的指派變量,然後動态代碼塊,然後構造方法,靜态代碼塊一次都沒有執行,一直等到第八次才執行出來。

那麼問題就來了,java中不是說靜态代碼塊是在類加載時便執行一次,可是這裡為什麼不執行。

解答這個問題還得在看一段代碼:

package com.basics.day10;

public class TestStatic {
    
    
    static {
        System.out.println("這時TestStatic的靜态代碼塊!");
    }
    
    {
        System.out.println("這是動态代碼塊!");
    }
    public TestStatic() {
        System.out.println("這是構造代碼塊");
    }
    
    public static void main(String[] args) {
        TestStatic te = new TestStatic();
        TestStatic t2 = new TestStatic();
    }
    
}
// 請注意看這個類中的靜态代碼塊
class Annoy {
    static {
        System.out.println("Annoy的靜态代碼塊");
    }
}
           

執行結果:

這時TestStatic的靜态代碼塊!
這是動态代碼塊!
這是構造代碼塊
這是動态代碼塊!
這是構造代碼塊
           

這個結果是不是就與我們所了解的一樣,但是重點不是在這裡,重點是我們聲明的第二類Annoy,裡邊的靜态代碼塊沒有被執行,這是為什麼呢???

呵呵,這還是類加載的問題,因為我們在主類中并沒有引用這個類,也就是這個類并沒有被加載進方法區,既然它沒有被加載,那麼其中的代碼塊自然就不會執行了。

這裡有一個很重要的概念,就是類的加載隻有在引用它的時候才會加載,而不是直接加載進方法區的,這也是類加載與構造對象的差別。構造對象并不是類加載,它們是兩個完全不一樣的概念,類加載要在對象構造之前。

到了這裡,我們折回到阿裡巴巴的那道題中,發現他在第二行的時候進行了對象的構造,而在這個構造中執行的都是非靜态的代碼塊。我是這樣了解的,在這個類的main函數被jvm識别的時候,這個類已經加載到方法區中去了,這時候靜态都會被執行一次,而且是按照聲明的順序執行的,正常下來,先k,t1,t2...跳過j,到靜态代碼塊,但這裡t1有了一個變故,那就是他構造了一個對象,這時類已經加載完成,相當于靜态已經預設被執行了一次,隻是順序上還沒有輪到,根據靜态的特性,他隻執行一次,是以t1這裡的分支,調用的就是動态代碼塊以及構造方法,不會去管那些靜态,這也就導緻出現了上邊的那種情況。

到此,這個問題就解決完成了。

繼續閱讀