1. 程式人生 > >深入理解Java Class檔案格式(七)

深入理解Java Class檔案格式(七)

本專欄列前面的一系列部落格, 對Class檔案中的一部分資料項進行了介紹。 本文將會繼續介紹class檔案中未講解的資訊。 先回顧一下上面一篇文章。 在上一篇部落格中, 我們介紹了:

this_class    對當前類的描述
super_class    對當前類的超類的描述
interfaces_count    當前類直接實現的介面的數量或當前介面直接繼承的介面的數量
interfaces  對當前類或當前介面直接實現或繼承的所有介面的描述
 

下面繼續介紹class檔案中的其他資訊。

class檔案中的fields_count和fields

fields_count描述的是當前的類中定義的欄位的個數, 注意, 這裡包括靜態欄位, 但不包括從父類繼承的欄位。 如果當前class檔案是由一個介面生成的, 那麼這裡的fields_count描述的是介面中定義的欄位, 我們知道, 介面中定義的欄位預設都是靜態的。此外要說明的是, 編譯器可能會自動生成欄位, 也就是說, class檔案中的欄位的數量可能多於原始檔中定義的欄位的數量。 舉例來說, 編譯器會為內部類增加一個欄位, 這個欄位是指向外圍類的物件的引用。
 
位於fields_count下面的資料叫做fields, 可以把它看做一個數組, 陣列中的每一項是一個field_info 。這個陣列中一共有fields_count個field_info , 每個field_info都是對一個欄位的描述。 下面我們詳細講解field_info的結構。 每個field_info的結構如下:


(1)access_flags
 其中access_flags佔兩個位元組, 描述的是欄位的訪問標誌資訊。 這裡就不在詳細介紹了, 下面給出一張表格(該表格來自《深入Java虛擬機器》):   

標誌位名稱 含義 設定者
ACC_PUBLIC 0x0001 欄位被設為public  類和介面
ACC_PRIVATE 0x0002 欄位被設為private
ACC_PROTECTED 0x0004 欄位被設為protected
ACC_STATIC 0x0008 欄位被設為static 類和介面
ACC_FINAL  0x0010 欄位被設為final 類和介面
ACC_VOLATILE 0x0040 欄位被設為volatile
ACC_TRANSIENT 0x0080 欄位被設為transient

(2)name_index
access_flags下面的兩個位元組是name_index, 這是一個指向常量池的索引, 它描述的是當前欄位的欄位名。 這個索引指向常量池中的一個CONSTANT_Utf8_info資料項。 這個CONSTANT_Utf8_info資料項中存放的字串就是當前欄位的欄位名。 


(3)descriptor_index
name_index下面的兩個位元組叫做descriptor_index , 它同樣是一個指向常量池的索引, 它描述的是當前欄位的描述符。 這個索引指向常量池中的一個CONSTANT_Utf8_info資料項。 這個CONSTANT_Utf8_info資料項中存放的字串就是當前欄位的描述符(關於欄位描述符, 在前面的部落格中已經有過詳細的講解, 如果不明白, 請參考前面的部落格:深入理解Java Class檔案格式(二))。 


(4)attributes_count和attributes
descriptor_index 下面是attributes_count和attributes 。 這是對當前欄位所具有的屬性的描述。 這裡的屬性和原始檔中的屬性不是同一個概念, 在原始檔測層面中, 屬性是欄位的另一種叫法, 希望讀者不要疑惑。讀者也不要輕視class檔案中的屬性, 這些屬性可以描述很多的資訊。 我們會在後面的文章中進行介紹。 

attributes_count表示這個欄位有幾個屬性。attributes 可以看成一個數組, 陣列中的每一項都是一個attribute_info , 每個attribute_info 表示一個屬性, 陣列中一共有attributes_count個屬性。可以出現在filed_info中的屬性有三種, 分別是ConstantValue, Deprecated, 和 Synthetic。 這些屬性會在後面的文章中進行介紹。


下面我們以程式碼的形式進行解釋, 原始碼如下:

package com.jg.zhang;
 
public class Programer extends Person{
 
    
    private Computer computer;
    
    public Programer(Computer computer){
        this.computer = computer;
    }
    
    public void doWork(){
        computer.calculate();
    }
}


反編譯之後, 常量池中會有如下資訊(這裡省略了大部分無關資訊):

Constant pool:
 
.........
.........
 
   #5 = Utf8               computer
   #6 = Utf8               Lcom/jg/zhang/Computer;
 
.........
.........
 
{
 
  private com.jg.zhang.Computer computer;
    flags: ACC_PRIVATE
 
.........
.........
 
}

從反編譯的結果可以看出, 原始檔中定義了一個Computer型別的欄位computer, 並且是private的。 然後常量池中有這個欄位的欄位名和描述符。 其中常量池第五項的CONSTANT_Utf8_info是欄位名, 第六項的CONSTANT_Utf8_info是該欄位的描述符。這裡有一點需要說明, 在反編譯Programer.class時,由於computer是私有的, 要加- private選項, 否則的話, 雖然常量池中有欄位引用資訊, 但是不會輸出欄位資訊, 即下面這兩行不會輸出:
  private com.jg.zhang.Computer computer;
    flags: ACC_PRIVATE

如果在javap中加入 - private選項, 那麼就會有上面兩行的輸出。 使用的命令如下:

      javap -c -v -private -classpath . com.jg.zhang.Programer

 根據反編譯的結果,可以下面給出示意圖, 該圖說明了與computer相對應的field_info是不合引用常量池的 ( 其中虛線範圍內表示常量池):

class檔案中的methods_count和methods

fields下面的資訊是methods_count和methods 。 methods_count描述的是當前的類中定義的方法的個數, 注意, 這裡包括靜態方法, 但不包括從父類繼承的方法。 如果當前class檔案是由一個介面生成的, 那麼這裡的methods_count描述的是介面中定義的抽象方法的數量, 我們知道, 介面中定義的方法預設都是公有的。此外需要說明的是, 編譯器可能會在編譯時向class檔案增加額外的方法, 也就是說, class檔案中的方法的數量可能多於原始檔中由使用者定義的方法。 舉例來說: 如果當前類沒有定義構造方法, 那麼編譯器會增加一個無引數的建構函式<init>; 如果當前類或介面中定義了靜態變數, 並且使用初始化表示式為其賦值, 或者定義了static靜態程式碼塊, 那麼編譯器在編譯的時候會預設增加一個靜態初始化方法<clinit> 。 
 
位於methods_count下面的資料叫做methods , 可以把它看做一個數組, 陣列中的每一項是一個method_info 。這個陣列中一共有methods_count個method_info , 每個method_info 都是對一個方法的描述。 下面我們詳細講解method_info 的結構。 每個method_info 的結構如下, 幾乎和field_info的結構是一樣的:


(1)access_flags
 其中access_flags佔兩個位元組, 描述的是方法的訪問標誌資訊。 這裡就不在詳細介紹了, 下面給出一張表格(該表格來自《深入Java虛擬機器》):    

標誌位名稱 標誌值 設定含義 設定者
ACC_PUBLIC 0x0001  方法設為public   類和介面
ACC_PRIVATE  0x0002 方法設為private
ACC_PROTECTED 0x0004 方法設為protected
ACC_STATIC 0x0008  方法設為static
ACC_FINAL 0x0010 方法設為final
ACC_SYNCHRONIZED  0x0020  方法設為sychronized
ACC_NATIVE 0x0100 方法設為native
ACC_ABSTRACT  0x0400 方法設為abstract  類和介面
ACC_STRICT  0x0800 方法設為strictFP 類和介面的<clinit>方法


(2)name_index
access_flags下面的兩個位元組是name_index, 這是一個指向常量池的索引, 它描述的是當前方法的方法名。 這個索引指向常量池中的一個CONSTANT_Utf8_info資料項。 這個CONSTANT_Utf8_info資料項中存放的字串就是當前方法的方法名。 


(3)descriptor_index
name_index下面的兩個位元組叫做descriptor_index , 它同樣是一個指向常量池的索引, 它描述的是當前方法的描述符。 這個索引指向常量池中的一個CONSTANT_Utf8_info資料項。 這個CONSTANT_Utf8_info資料項中存放的字串就是當前方法的描述符(關於方法描述符, 在前面的部落格中已經有過詳細的講解, 如果不明白, 請參考前面的部落格: 深入理解Java Class檔案格式(二))。 


(4)attributes_count和attributes
descriptor_index 下面是attributes_count和attributes 。 這是對當前方法所具有的屬性的描述。 這裡的屬性和原始檔中的屬性不是同一個概念, 在原始檔測層面中, 屬性是欄位的另一種叫法, 希望讀者不要疑惑。讀者也不要輕視class檔案中的屬性, 這些屬性可以描述很多的資訊。 我們會在後面的文章中進行介紹。 

attributes_count表示這個欄位有幾個屬性。attributes 可以看成一個數組, 陣列中的每一項都是一個attribute_info , 每個attribute_info 表示一個屬性, 陣列中一共有attributes_count個屬性。可以出現在method_info 中的屬性有三種, 分別是Code, Deprecated, Exceptions 和Synthetic。 在這幾個屬性中, 尤其是Code和Exceptions 非常重要, 這兩個屬性對於在class檔案中完整描述一個方法起著至關重要的作用, 其中Code屬性中存放方法的位元組面指令,Exceptions 屬性是對方法宣告中丟擲的異常的描述 。 這兩屬性以及其他一些屬性, 會在下一篇文章中詳細介紹, 敬請關注。


介紹完了每個method_info的結構, 下面我們以程式碼來說明, 還是使用上面的原始碼:

package com.jg.zhang;
 
public class Programer extends Person{
 
    
    private Computer computer;
    
    public Programer(Computer computer){
        this.computer = computer;
    }
    
    public void doWork(){
        computer.calculate();
    }
}

反編譯之後, 常量池中會有如下資訊(這裡省略了大部分無關資訊):

Constant pool:
 
.........
 
   #7 = Utf8               <init>
   #8 = Utf8               (Lcom/jg/zhang/Computer;)V
 
.........
 
  #12 = Utf8               ()V
 
.........
 
  #19 = Utf8               doWork
 
{
 
.........
 
  public com.jg.zhang.Programer(com.jg.zhang.Computer);
    flags: ACC_PUBLIC
 
.........
 
  public void doWork();
    flags: ACC_PUBLIC
 
.........
}

由反編譯結果可以看出, 該類中定義了兩個方法, 其中一個是構造方法, 一個是doWork方法, 且這兩個方法都是public的。 這兩個方法的描述資訊都存放在常量池。 其中第7項的CONSTANT_Utf8_info為構造方法的方法名, 第8項的CONSTANT_Utf8_info為構造方法的方法描述符, 第19項的CONSTANT_Utf8_info為doWork方法的方法名, 第12項的CONSTANT_Utf8_info為doWork方法的方法描述符。 

根據常量池中的資訊, 可以得出如下的示意圖, 該示意圖形象的說明了class檔案中的method_info是如何引用常量池中的資料項來描述當前類中定義的方法的。 圖中虛線範圍內表示常量池所在的區域:

總結

到此為止, 我們就介紹完了class檔案中的fields和methods, 進行一下總結。 

 fields是對當前類中定義的欄位的描述, 其中每個欄位使用一個field_info表示, fields中有fields_count個field_info。

methods是對當前類或者介面中宣告的方法的描述, 其中每個方法使用一個method_info表示, methods中有methods_count個method_info。 

在下一篇部落格中, 將會介紹class檔案中的各個屬性, 敬請關注。