程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 通過Java字節碼發現有趣的內幕

通過Java字節碼發現有趣的內幕

編輯:關於JAVA

 關於類初始化過程網上有很多相關的文章,其實也算是學習語言時一個基礎知識,但今天我想從字節碼表現上更深入的來理解各種場景下的實例初始化過程是怎麼樣的,從簡單到復雜大體分為下面幾個場景 。

1、成員+構造函數

2、成員+代碼塊+構造函數

3、 靜態變量+靜態代碼塊

4、 繼承和多態

首先明確下運行環境:

 

我們先來看一下第一個場景: 成員+構造函數,也是我們最經常使用到的場景。在這個場景中想通過字節碼了解下成員屬性的初始情況,下圖左邊代碼 聲明了四個類成員屬性和一個無入參構造函數 ,右邊是對應的執行字節碼棧幀執行順序。

 

1、在字節碼(1)處隱示的調用了類的父默認構造函數,這個很重要,決定了類的多重初始化過程,詳細在最後一個場景展開;

2、代碼中myId1和myId2屬性聲明方式不同,初始化過程也是不一樣的,如圖中所示,myId1在聲明屬性時未進行任何的指令操作,而是等到構造函數中的myId1=100時才有執行指令,而像myId2在聲明就進行賦值指令,所以myId2會被myId2優先初始化。

3、myText2在聲明時賦於null,所以我們可以看到指令也會進行aconst_null的操作,但是在(6)時再次對myText2進行了賦值並再次產生了指令操作。注:null本身不是一種對象,在JVM中沒有明確的指明采用什麼類型,不同的JVM實現可能不一樣,我們可以簡單理解為null是一個標志,告訴虛擬機對應的類型還不明確,並還未為其分配空間。

4、從這個場景圖的右邊字節碼指令執行過程我們可以總結出:

  • 有賦值的類 成員屬性是按聲明的位置先後進行初始化(與訪問標志符無關),如圖(2)(3);

  • 成員屬性的初始化會優先於構造函數的初始化,如圖(3)(6);

  • 初始化動作都是在構造函數中完成的, 如果沒有顯示構造函數,那麼編譯器會產生一個無入參構造函數來完成初始工作;

  • 建議聲明成員屬性時沒有必要賦於null,等到真實需要使用成成員時再初始化或傳遞值;

再來看第二個場景,如果我們的類中有非靜態的代碼塊時,整體初始化是怎麼樣的,先來看下面的代碼示例兩個print方法最終會輸出什麼內容。

 

最終會輸出:

text:null

text:text1-1

我們從字節碼上分析一下為什麼會是這個輸出結果。

 

1、從圖的字節碼上可以看出,非靜態代碼塊的執行最終也是被放進構造函數中完;

2、代碼塊與成員屬性的初始化順序也是按其在代碼中出現的先後順序,如(2)(3)所示;

3、所以在執行(1)print時,實際上myText1還沒有被任何的初始化,包括成員屬性的賦值,所以這時輸出text:null;

4、實際上 myText1是在(2)才有值,但緊接著還會被(3)給替換,所 在執行(4)時輸出text:text1-1;

5、該場景總結:

  • 非靜態代碼塊的執行也是被放到構造函數中。

  • 非靜態代碼塊並不影響代碼順序的初始化工作

  • 盡量不要有非靜態的代碼塊,可讀性不好,需要在非靜態代碼塊解決的問題完全可以移到構造函數中。

接下來我們來看第三個場景,同樣我們先來看一個代碼輸出結果 ,下面的代碼最終輸出的什麼?

 

我們再來一下靜態變量與靜態代碼段的字節碼是什麼樣的:

 

1、從圖上我們發現靜態量和靜態代碼塊編譯後都整合到一個static段中,如(1)(2)(3),這個段中的字節碼執行順序就是靜態變量和靜態代碼塊在源代碼中的出現順序,而且該static{}最終在虛擬機加載類時調用一次;

2、而(4)(5)雖然實例化兩個對象,但與static{}裡的執行碼沒有任何的關系;

3、靜態變量和靜態代碼塊的聲明順序決定了引用順序,比如圖上代碼中的(6)是一個不合法的引用,因為myStaticId2在其後面聲明;

4、總結 :上面執行結果是一個200,是的就一個,因為靜態變量和靜態代碼塊與所在的類被實例化個數無關,而是所在類被虛擬機加載時會執行對應的靜態代碼塊的字節碼,這是類被加載事件觸發的,並會因為類實例才會有,比如第一次執行下面的非實例化的代碼同樣會觸發該類的靜態代碼塊執行。

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