本專欄列前面的一系列博客, 對Class文件中的一部分數據項進行了介紹。 本文將會繼續介紹class文件中未講解的信息。 先回顧一下上面一篇文章。 在上一篇博客中, 咱們介紹了:java
this_class 對當前類的描述
super_class 對當前類的超類的描述
interfaces_count 當前類直接實現的接口的數量或當前接口直接繼承的接口的數量
interfaces 對當前類或當前接口直接實現或繼承的全部接口的描述
數組
下面繼續介紹class文件中的其餘信息。函數
class文件中的fields_count和fieldsthis
fields_count描述的是當前的類中定義的字段的個數, 注意, 這裏包括靜態字段, 但不包括從父類繼承的字段。 若是當前class文件是由一個接口生成的, 那麼這裏的fields_count描述的是接口中定義的字段, 咱們知道, 接口中定義的字段默認都是靜態的。此外要說明的是, 編譯器可能會自動生成字段, 也就是說, class文件中的字段的數量可能多於源文件中定義的字段的數量。 舉例來講, 編譯器會爲內部類增長一個字段, 這個字段是指向外圍類的對象的引用。
位於fields_count下面的數據叫作fields, 能夠把它看作一個數組, 數組中的每一項是一個field_info 。這個數組中一共有fields_count個field_info , 每一個field_info都是對一個字段的描述。 下面咱們詳細講解field_info的結構。 每一個field_info的結構以下:spa
(1)access_flags
其中access_flags佔兩個字節, 描述的是字段的訪問標誌信息。 這裏就不在詳細介紹了, 下面給出一張表格(該表格來自《深刻Java虛擬機》): code
標誌位名稱 | 值 | 含義 | 設定者 |
---|---|---|---|
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文件格式(二))。 blog
(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文件中的各個屬性, 敬請關注。