首先, 讓我們回顧一下關於class文件格式的之前兩篇博客的主要內容。 在 深入理解Java Class文件格式(一) 中, 講解了class文件在整個java體系結構中的位置和作用, 講解了class文件中的魔數和版本號相關的信息, 並且對常量池進行了概述。 在 深入理解Java Class文件格式(二) 中, 主要講解了class文件中的特殊字符串, 包括類的全限定名, 字段描述符和方法描述符, 這些特殊字符串大量出現在class文件的常量池中, 是理解常量池的基礎。 本文會詳細講解常量池中的各個數據項。
常量池中各數據項類型詳解
關於常量池的大概內容, 已經在 深入理解Java Class文件格式(一) 中講解過了, 這篇文章中還介紹了常量池中的11種數據類型。 本文的任務是詳細講解這11種數據類型, 深度剖析源文件中的各種信息是以什麼方式存放在常量池中的。
我們知道, 常量池中的數據項是通過索引來引用的, 常量池中的各個數據項之間也會相互引用。在這11中常量池數據項類型中, 有兩種比較基礎, 之所以說它們基礎, 是因爲這兩種類型的數據項會被其他類型的數據項引用。 這兩種數據類型就是CONSTANT_Utf8 和 CONSTANT_NameAndType , 其中CONSTANT_NameAndType類型的數據項(CONSTANT_NameAndType_info)也會引用CONSTANT_Utf8類型的數據項(CONSTANT_Utf8_info) 。 與其他介紹常量池的書籍或其他資料不同, 本着循序漸進和先後分明的原則, 我們首先對這兩種比較基本的類型的數據項進行介紹, 然後再依次介紹其他9中數據項。
(1) CONSTANT_Utf8_info
- 程序中的字符串常量
- 常量池所在當前類(包括接口和枚舉)的全限定名
- 常量池所在當前類的直接父類的全限定名
- 常量池所在當前類型所實現或繼承的所有接口的全限定名
- 常量池所在當前類型中所定義的字段的名稱和描述符
- 常量池所在當前類型中所定義的方法的名稱和描述符
- 由當前類所引用的類型的全限定名
- 由當前類所引用的其他類中的字段的名稱和描述符
- 由當前類所引用的其他類中的方法的名稱和描述符
- 與當前class文件中的屬性相關的字符串, 如屬性名等
總結一下, 其中有這麼五類: 程序中的字符串常量, 類型的全限定名, 方法和字段的名稱, 方法和字段的描述符, 屬性相關字符串。 程序中的字符串常量不用多說了, 我們經常使用它們創建字符串對象, 屬性相關的字符串, 等到講到class中的屬性信息(attibute)時自會提及。 方法和字段的名稱也不用多說了 。 剩下的就是類型的全限定名,方法和字段的描述符, 這就是上篇文章中提及的"特殊字符串", 不熟悉的同學可以先讀一下上篇文章 深入理解Java Class文件格式(二) 。 還有一點需要說明, 類型的全限定名, 方法和字段的名稱, 方法和字段的描述符, 可以是本類型中定義的, 也可能是本類中引用的其他類的。
package com.jg.zhang;
public class Programer extends Person {
static String company = "CompanyA";
static{
System.out.println("staitc init");
}
String position;
Computer computer;
public Programer() {
this.position = "engineer";
this.computer = new Computer();
}
public void working(){
System.out.println("coding...");
computer.working();
}
}
#2 = Utf8 com/jg/zhang/Programer //當前類的全限定名
#4 = Utf8 com/jg/zhang/Person //父類的全限定名
#5 = Utf8 company //company字段的名稱
#6 = Utf8 Ljava/lang/String; //company和position字段的描述符
#7 = Utf8 position //position字段的名稱
#8 = Utf8 computer //computer字段的名稱
#9 = Utf8 Lcom/jg/zhang/Computer; //computer字段的描述符
#10 = Utf8 <clinit> //類初始化方法(即靜態初始化塊)的方法名
#11 = Utf8 ()V //working方法的描述符
#12 = Utf8 Code //Code屬性的屬性名
#14 = Utf8 CompanyA //程序中的常量字符串
#19 = Utf8 java/lang/System //所引用的System類的全限定名
#21 = Utf8 out //所引用的out字段的字段名
#22 = Utf8 Ljava/io/PrintStream; //所引用的out字段的描述符
#24 = Utf8 staitc init //程序中的常量字符串
#27 = Utf8 java/io/PrintStream //所引用的PrintStream類的全限定名
#29 = Utf8 println //所引用的println方法的方法名
#30 = Utf8 (Ljava/lang/String;)V //所引用的println方法的描述符
#31 = Utf8 LineNumberTable //LineNumberTable屬性的屬性名
#32 = Utf8 LocalVariableTable //LocalVariableTable屬性的屬性名
#33 = Utf8 <init> //當前類的構造方法的方法名
#41 = Utf8 com/jg/zhang/Computer //所引用的Computer類的全限定名
#45 = Utf8 this //局部變量this的變量名
#46 = Utf8 Lcom/jg/zhang/Programer; //局部變量this的描述符
#47 = Utf8 working //woking方法的方法名
#49 = Utf8 coding... //程序中的字符串常量
#52 = Utf8 SourceFile //SourceFile屬性的屬性名
#53 = Utf8 Programer.java //當前類所在的源文件的文件名
(2) CONSTANT_NameAndType類型的數據項
package com.jg.zhang;
public class Person {
int age;
int getAge(){
return age;
}
}
#1 = Class #2 // com/jg/zhang/Person
#2 = Utf8 com/jg/zhang/Person
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 age
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Methodref #3.#11 // java/lang/Object."<init>":()V
#11 = NameAndType #7:#8 // "<init>":()V
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/jg/zhang/Person;
#16 = Utf8 getAge
#17 = Utf8 ()I
#18 = Fieldref #1.#19 // com/jg/zhang/Person.age:I
#19 = NameAndType #5:#6 // age:I
#20 = Utf8 SourceFile
#21 = Utf8 Person.java
常量池一共有21項, 我們可以看到, 一共有兩個CONSTANT_NameAndType_info 數據項, 分別是第#11項和第#19項, 其中第#11項的CONSTANT_NameAndType_info又引用了常量池中的第#7項和第#8項, 被引用的這兩項都是CONSTANT_Utf8_info , 它們中存儲的字符串常量值分別是 <init> 和 ()V。 其實他們加起來表示的就是父類Object的構造方法。 那麼這裏爲什麼會是父類Object的構造方法而不是本類的構造方法呢? 這是因爲類中定義的方法如果不被引用(也就是說在當前類中不被調用), 那麼常量池中是不會有相應的 CONSTANT_NameAndType_info 與之對應的, 只有引用了一個方法, 纔有相應的CONSTANT_NameAndType_info 與之對應。 這也是爲什麼說CONSTANT_NameAndType_info 是方法的符號引用的一部分的原因。 (這裏提到一個新的概念, 叫做方法的符號引用, 這個概念會在後面的博客中進行講解) 可以看到, 在源碼存在兩個方法, 分別是編譯器默認添加的構造方法和我們自己定義的getAge方法, 因爲並沒有在源碼中顯示的調用這兩個方法,所以在常量池中並不存在和這兩個方法相對應的CONSTANT_NameAndType_info 。 之所以會存在父類Object的構造方法對應的CONSTANT_NameAndType_info , 是因爲子類構造方法中會默認調用父類的無參數構造方法。 我們將常量中的其他信息去掉, 可以看得更直觀:
int getAge(){
return age;
}
總結
更多關於深入理解Java的文章, 請關注我的專欄 : http://blog.csdn.net/column/details/zhangjg-java-blog.html
更多關於Java和Android等其他技術的文章, 請關注我的博客: http://blog.csdn.net/zhangjg_blog