程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java虛擬機類加載機制——案例分析

Java虛擬機類加載機制——案例分析

編輯:JAVA綜合教程

Java虛擬機類加載機制——案例分析


在《Java虛擬機類加載機制》一文中詳細闡述了類加載的過程,並舉了幾個例子進行了簡要分析,在文章的最後留了一個懸念給各位,這裡來揭開這個懸念。建議先看完《Java虛擬機類加載機制》這篇再來看這個,印象會比較深刻,如若不然,也沒什麼關系~~
下面是程序代碼:

package jvm.classload;

public class StaticTest
{
    public static void main(String[] args)
    {
        staticFunction();
    }

    static StaticTest st = new StaticTest();

    static
    {
        System.out.println("1");
    }

    {
        System.out.println("2");
    }

    StaticTest()
    {
        System.out.println("3");
        System.out.println("a="+a+",b="+b);
    }

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

    int a=110;
    static int b =112;
}

問題是:請問這段程序的輸出是什麼?
這個是我在論壇上看到的一個問題,我覺得比較金典。
一般對於這類問題,小伙伴們腦海中肯定浮現出這樣的knowledge:

Java中賦值順序:
1. 父類的靜態變量賦值
2. 自身的靜態變量賦值
3. 父類成員變量賦值
4. 父類塊賦值
5. 父類構造函數賦值
6. 自身成員變量賦值
7. 自身塊賦值
8. 自身構造函數賦值

ok,按照這個理論輸出是什麼呢?答案輸出:1 4,這樣正確嚒?肯定不正確啦,這裡不是說上面的規則不正確,而是說不能簡單的套用這個規則。
正確的答案是:

2
3
a=110,b=0
1
4

是不是有點不可思議?且聽我一一道來,這裡主要的點之一:實例初始化不一定要在類初始化結束之後才開始初始化。
類的生命周期是:加載->驗證->准備->解析->初始化->使用->卸載,只有在准備階段和初始化階段才會涉及類變量的初始化和賦值,因此只針對這兩個階段進行分析;
類的准備階段需要做是為類變量分配內存並設置默認值,因此類變量st為null、b為0;(需要注意的是如果類變量是final,編譯時javac將會為value生成ConstantValue屬性,在准備階段虛擬機就會根據ConstantValue的設置將變量設置為指定的值,如果這裡這麼定義:static final int b=112,那麼在准備階段b的值就是112,而不再是0了。)
類的初始化階段需要做是執行類構造器(類構造器是編譯器收集所有靜態語句塊和類變量的賦值語句按語句在源碼中的順序合並生成類構造器,對象的構造方法是(),類的構造方法是(),可以在堆棧信息中看到),因此先執行第一條靜態變量的賦值語句即st = new StaticTest (),此時會進行對象的初始化,對象的初始化是先初始化成員變量再執行構造方法,因此打印2->設置a為110->執行構造方法(打印3,此時a已經賦值為110,但是b只是設置了默認值0,並未完成賦值動作),等對象的初始化完成後繼續執行之前的類構造器的語句,接下來就不詳細說了,按照語句在源碼中的順序執行即可。
這裡面還牽涉到一個冷知識,就是在嵌套初始化時有一個特別的邏輯。特別是內嵌的這個變量恰好是個靜態成員,而且是本類的實例。
這會導致一個有趣的現象:“實例初始化竟然出現在靜態初始化之前”。
其實並沒有提前,你要知道java記錄初始化與否的時機。
看一個簡化的代碼,把關鍵問題解釋清楚:

public class Test {
    public static void main(String[] args) {
        func();
    }
    static Test st = new Test();
    static void func(){}
}

??根據上面的代碼,有以下步驟:

首先在執行此段代碼時,首先由main方法的調用觸發靜態初始化。 在初始化Test 類的靜態部分時,遇到st這個成員。 但湊巧這個變量引用的是本類的實例。 那麼問題來了,此時靜態初始化過程還沒完成就要初始化實例部分了。是這樣麼? 從人的角度是的。但從java的角度,一旦開始初始化靜態部分,無論是否完成,後續都不會再重新觸發靜態初始化流程了。 因此在實例化st變量時,實際上是把實例初始化嵌入到了靜態初始化流程中,並且在樓主的問題中,嵌入到了靜態初始化的起始位置。這就導致了實例初始化完全至於靜態初始化之前。這也是導致a有值b沒值的原因。 最後再考慮到文本順序,結果就顯而易見了。

??詳細看到這裡,心中大概有個結論了吧,如果對於類的加載機制比較模糊的話,可以參考開篇推薦的博文~ 有問題歡迎留言。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved