我們知道javac
命令可以將 .java
文件編譯成 .class
文件乱豆,而這個Class 文件
中包含了Java虛擬機指令集奖恰、符號表以及若干其他輔助信息;最終將在Java虛擬機運行宛裕。
- Java虛擬機最終運行的是
Class 文件
瑟啃,它其實不管你是不是由.java
文件生成的,如果有別的程序語言能將自己程序邏輯翻譯成一個Class 文件
揩尸,那么它也可以運行在 Java虛擬機 上的岩榆。- 也就是說Java虛擬機其實并不和
java
語言強綁定勇边,它只與Class
這個二進制文件強關(guān)聯(lián)识颊,Class 文件
包含程序運行的所有信息。
本文是以 JVM8
為例的奕坟。
一. class 文件格式
每一個 Class文件
都有如下的 ClassFile
文件結(jié)構(gòu):
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- 每一個
Class文件
都對應(yīng)著唯一一個類或接口的定義信息曙砂,但是反過來說退个,類或接口并不一定都得定義在文件里(比如類或接口也可以通過類加載器直接生成)砌滞。Class文件
并不一定是一個文件歇父,它指的是是一組以8
個字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在文件之中沙合,中間沒有添加任何分隔符奠伪,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù),沒有空隙存在首懈。多字節(jié)數(shù)據(jù)項總是按照big-endian
(大端在前)的順序存儲绊率。Class文件
有兩種數(shù)據(jù)格式:
- 一種是固定字節(jié)數(shù)的數(shù)據(jù)結(jié)構(gòu),稱之為項(
Items
)究履,其實就是無符號數(shù)。例如上圖u2
表示兩個字節(jié)無符號數(shù)(范圍0 -> 65536
)炊甲。- 一種是可變長度字節(jié)數(shù)的數(shù)據(jù)結(jié)構(gòu)菱父,稱之為表(
Tables
),用來記錄復(fù)合數(shù)據(jù)結(jié)構(gòu),都是以_info
結(jié)尾鳖轰。例如上圖中的cp_info
,field_info
,method_info
和attribute_info
。
先簡單介紹一下 ClassFile
文件結(jié)構(gòu)各部分含義:
-
magic
:Class文件
的魔數(shù),代表這個二進制文件是Class文件
,它的固定是0xCAFEBABE
,不會改變智亮。 -
minor_version
和major_version
: 表示這個Class文件
的副版本號和主版本號状原。 -
constant_pool_count
: 表示常量池的數(shù)量。它的類型是
u2
,也就是說一個Class文件
最多有65536 -1
個常量磨澡。 -
constant_pool[constant_pool_count-1]
: 表示常量池厦酬,記錄這個Class文件
所有常量信息国夜。- 常量池包含
Class文件
及其子結(jié)構(gòu)中所有的常量信息窄驹。 - 常量池中的每一項都有一個特征,第一個字節(jié)表示這個常量的類型,在
JVM8
中一共有14
中類型。 - 常量池的索引范圍是
1 -> constant_pool_count -1
花吟,也就是constant_pool_count -1
個常量谬墙,索引0
表示不是任何常量。
- 常量池包含
-
access_flags
: 表示這個Class文件
訪問標(biāo)志。- 我們知道在
java
語言中,類,方法,成員字段都可以設(shè)置不同的修飾符,這里的訪問標(biāo)志就表示修飾符相關(guān)信息镐躲。 - 它的類型是
u2
,有16
位二進制數(shù)端礼,也就是說它最多可以表示16
個不同的訪問標(biāo)志凡桥。
- 我們知道在
-
this_class
: 表示當(dāng)前類或者接口的類常量索引啊掏。它的類型是
u2
娜睛,就是表示常量池中某項的有效索引值,而且它指向的那個常量必須是CONSTANT_Class_info
類型。 -
super_class
: 表示當(dāng)前類或者接口的父類常量索引。- 它的類型也是
u2
,也表示常量池中某項的有效索引值。但是根據(jù)當(dāng)前Class文件
是類或者接口,分為兩種情況:- 如果當(dāng)前是類的話阿迈,當(dāng)
super_class
值是0
待逞,那就表示這個類是Object
類牺荠;其他的情況,super_class
值都是常量池中一個CONSTANT_Class_info
類型常量的索引值驰凛。 - 如果當(dāng)前是接口的話胚宦,那么
super_class
值只會是常量池中一個CONSTANT_Class_info
類型常量的索引值,而且這個CONSTANT_Class_info
類型表示Object
類轴捎,因為接口的父類就只能是Object
。
- 如果當(dāng)前是類的話阿迈,當(dāng)
- 它的類型也是
-
interfaces_count
: 表示當(dāng)前類實現(xiàn)的接口數(shù)量汇竭,或者當(dāng)前接口繼承的接口數(shù)量皂甘。它的類型是
u2
,表示最多有65536
個接口;如果是0
表示沒有任何接口。 -
interfaces[interfaces_count]
: 表示接口對應(yīng)的常量池索引值表,長度就是interfaces_count
。 -
fields_count
: 表示當(dāng)前類或者接口的字段數(shù)量,包括靜態(tài)字段和成員字段。 -
fields[fields_count]
: 表示當(dāng)前類或者接口的字段詳細(xì)信息的表,長度是fields_count
。它的類型是
field_info
,記錄著這個字段的所有信息乾戏。 -
methods_count
: 表示當(dāng)前類或者接口的方法數(shù)量念搬,包括類方法和實例方法悯搔。 -
methods[methods_count]
: 表示當(dāng)前類或者接口的方法詳細(xì)信息的表灌曙,長度是fields_count
蚣驼。它的類型是
method_info
,記錄著這個方法的所有信息相艇。 -
attributes_count
: 表示當(dāng)前類或者接口的屬性數(shù)量颖杏。 -
attributes[attributes_count]
: 表示當(dāng)前類或者接口的屬性詳細(xì)信息的表。它的類型是
attribute_info
坛芽,屬性的種類非常多,不僅在類中有茵宪,在字段詳情field_info
和方法詳情method_info
中也有相關(guān)屬性表黍聂。這個我們后面會慢慢說明。
二. 各類名稱在 Class 文件
中的表示形式
- 全限定名
類和接口的名稱都是全限定形式,被稱為二進制名稱瑟枫,不過和
java
語言中不同室埋,它使用(/
) 而不是 (.
) 作為分隔符的,例如java.lang.Object
就變成了java/lang/Object
。 - 非限定名
- 方法名溯祸,字段名,局部變量名和形式參數(shù)名都是非限定形式。
- 非限定名至少有一個
Unicode
字符,但是不能是ASCII
字符 (. ; [ /
) 這四個字符。 - 除了實例初始化方法
<init>
和類初始化方法<clinit>
以外捐腿,其他非限定名也不能出現(xiàn) (< >
) 這兩個字符丹拯。
三. 描述符
描述符是表示字段或方法類型的字符串。
3.1 字段描述符
字段描述符表示類必孤、實例或局部變量的類型蔚龙。
字段描述符語法:
1. FieldDescriptor -> FieldType
2. FieldType -> BaseType | ObjectType | ArrayType
3. BaseType -> B | C | D | F | I | J | S | Z
4. ObjectType -> LClassName;
5. ArrayType -> [ComponentType
6. ComponentType -> FieldType
這個語法中眠寿,
B C D F I J S Z L ; [
是終結(jié)符,其他的都是非終結(jié)符焦蘑。這個方面不清楚的請看我的 編譯原理-文法定義 相關(guān)文章說明盯拱。
從上面文法可以看出,字段描述符中一共有三個類型:
-
BaseType
表示基礎(chǔ)類型。- 一共分為八種狡逢,分別是
B C D F I J S Z
宁舰。 - 其中
Z
表示boolean
類型,因為B
已經(jīng)表示byte
類型了奢浑;J
表示long
類型蛮艰,因為L
這個字符被引用類型用了。
- 一共分為八種狡逢,分別是
-
ObjectType
表示引用類型雀彼。它的格式是
LClassName;
壤蚜,即以L
開頭,;
結(jié)尾徊哑,中間是類的全限定名袜刷。例如Object
類就是Ljava/lang/Object;
。 -
ArrayType
表示數(shù)組類型莺丑。它的格式是
[ComponentType
著蟹,即以[
開頭,ComponentType
表示三個類型中任意類型窒盐。例如int[]
就是[I
;int[][]
就是[[I
;String[]
就是[Ljava/lang/String;
草则。
3.2 方法描述符
方法描述符包含0
個或者多個參數(shù)描述符以及一個返回值描述符。
方法描述符語法:
1. MethodDescriptor -> ({ParameterDescriptor}) ReturnDescriptor
2. ParameterDescriptor-> FieldType
3. ReturnDescriptor-> FieldType | VoidDescriptor
4. VoidDescriptor-> V
- 這個語法中
( ) V
這個三個字符是終結(jié)符蟹漓。{ParameterDescriptor}
中的{ }
表示這個非終結(jié)符ParameterDescriptor
能夠出現(xiàn)0
次或者多次。V
表示沒有任何返回值源内,就是void
關(guān)鍵字葡粒。- 例如方法
String test(int i, long l, Integer i1, Long l1, Object[] objs) {..}
對應(yīng)的描述符(IJLjava/lang/Integer;Ljava/lang/Long;[Ljava/lang/Object;)Ljava/lang/String;
看了描述符,可能大家有點疑惑膜钓,泛型信息怎么表示啊?
描述符的確不能記錄泛型相關(guān)信息嗽交,泛型信息記錄在
Signatures
屬性中。
四. 常量池
常量池的通用格式如下:
cp_info {
u1 tag;
u1 info[];
}
一個字節(jié)無符號數(shù)
tag
表示常量類型颂斜;info
表示不同常量類型的數(shù)據(jù)夫壁。
目前 JVM8
中一共用14
種常量類型,分別如下:
Constant Type | Value | 描述 |
---|---|---|
CONSTANT_Utf8 | 1 | 表示 Utf8 編碼的字符串 |
CONSTANT_Integer | 3 | 表示整形字面量 |
CONSTANT_Float | 4 | 表示單精度浮點型字面量 |
CONSTANT_Long | 5 | 表示長整形字面量 |
CONSTANT_Double | 6 | 表示雙精度浮點型字面量 |
CONSTANT_Class | 7 | 表示類或者接口的符號引用 |
CONSTANT_String | 8 | 表示字符串類型字面量 |
CONSTANT_Fieldref | 9 | 表示字段的符號引用 |
CONSTANT_Methodref | 10 | 表示類中方法的符號引用 |
CONSTANT_InterfaceMethodref | 11 | 表示接口中方法的符號引用 |
CONSTANT_NameAndType | 12 | 表示字段或者方法的名稱和描述符 |
CONSTANT_MethodHandle | 15 | 表示方法的句柄 |
CONSTANT_MethodType | 16 | 表示方法的類型 |
CONSTANT_InvokeDynamic | 18 | 表示動態(tài)計算常量 |
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
4.1 CONSTANT_Utf8_info
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
-
tag
: 表示常量類型沃疮,值就是1
(CONSTANT_Utf8
) -
length
: 表示Utf8
字符串的總字節(jié)數(shù)盒让。它的類型是
u2
,也就是Utf8
字符串最多只能有65536
個字節(jié)司蔬。 -
bytes[length]
: 表示Utf8
字符串的字節(jié)數(shù)組數(shù)據(jù)邑茄。
4.2 CONSTANT_Integer_info
和 CONSTANT_Float_info
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
-
tag
: 表示常量類型。-
CONSTANT_Integer_info
的值就是3
(CONSTANT_Integer
)俊啼。 -
CONSTANT_Float_info
的值就是4
(CONSTANT_Float
)肺缕。
-
-
bytes
: 是一個四個字節(jié)數(shù),用來儲存int
和float
類型字面量。
4.3 CONSTANT_Long_info
和 CONSTANT_Double_info
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
-
tag
: 表示常量類型同木。-
CONSTANT_Long_info
的值就是5
(CONSTANT_Long
)浮梢。 -
CONSTANT_Double_info
的值就是6
(CONSTANT_Double
)。
-
-
high_bytes
和low_bytes
: 組成一個8
個字節(jié)數(shù)彤路,來儲存long
和double
類型字面量黔寇。 - 特別注意,這兩種類型會在常量表中占據(jù)兩個索引斩萌,也就是如果索引
3
處的CONSTANT_Long_info
類型缝裤,那么下一個有效索引是5
。
4.4 CONSTANT_Class_info
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
-
tag
: 表示常量類型颊郎,CONSTANT_Class_info
的值就是7
(CONSTANT_Class
)憋飞。 -
name_index
: 表示類符號引用的索引。值必須是當(dāng)前常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引姆吭。
4.5 CONSTANT_String_info
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
-
tag
: 表示常量類型榛做,CONSTANT_String_info
的值就是8
(CONSTANT_String
)。 -
string_index
: 表示字符串值的索引内狸。值必須是當(dāng)前常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引检眯。
4.6 CONSTANT_Fieldref_info
, CONSTANT_Methodref_info
和 CONSTANT_InterfaceMethodref_info
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
-
tag
: 表示常量類型。-
CONSTANT_Fieldref_info
的值就是9
(CONSTANT_Fieldref
); -
CONSTANT_Methodref_info
的值就是10
(CONSTANT_Methodref
); -
CONSTANT_InterfaceMethodref_info
的值就是11
(CONSTANT_InterfaceMethodref
)昆淡。
-
-
class_index
: 表示這個字段或者方法所屬類符號引用的索引锰瘸。- 值必須是當(dāng)前常量池中一個
CONSTANT_Class_info
類型常量的有效索引。
- 值必須是當(dāng)前常量池中一個
-
name_and_type_index
: 表示這個字段或者方法的名稱和描述符的索引昂灵。- 值必須是當(dāng)前常量池中一個
CONSTANT_NameAndType_info
類型常量的有效索引避凝。 - 知道字段的名稱和描述符,就知道了字段的名字和類型眨补。
- 知道方法的名稱和描述符管削,就知道了方法的名字和方法參數(shù)類型以及方法返回值類型。
- 值必須是當(dāng)前常量池中一個
我們知道要使用一個字段或者調(diào)用一個方法撑螺,就必須知道字段或者方法所屬類符號引用含思,和字段的名字和類型,方法的名字和方法參數(shù)類型以及方法返回值類型甘晤。
但是我們知道類是能繼承的含潘,那么子類調(diào)用父類的方法或者字段,這里的所屬類符號引用安皱,到底是子類本身還是父類的呢调鬓?
請大家思考一下,后面的例子中酌伊,我們將會講解腾窝。
4.7 CONSTANT_NameAndType_info
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
-
tag
: 表示常量類型缀踪,CONSTANT_NameAndType_info
的值就是12
(CONSTANT_NameAndType
)。 -
name_index
: 表示字段或者方法名稱虹脯。值必須是當(dāng)前常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引驴娃。 -
descriptor_index
: 表示字段或者方法描述符。值必須是當(dāng)前常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引循集。
4.8 CONSTANT_MethodHandle_info
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
-
tag
: 表示常量類型唇敞,CONSTANT_MethodHandle_info
的值就是15
(CONSTANT_MethodHandle
)。 -
reference_kind
:表示方法句柄的類型咒彤。值范圍是
0 ~ 9
疆柔,不同的值決定了句柄字節(jié)碼的行為。 -
reference_index
:就是常量池中的一個有效索引镶柱,因為reference_kind
值的不同旷档,分為幾種情況:- 如果
reference_kind
的值是1
(REF_getField
),2
(REF_getStatic
),3
(REF_putField
), 或者4
(REF_putStatic
);都是和字段相關(guān)歇拆,那么reference_index
必須是常量池中CONSTANT_Fieldref_info
類型常量的有效索引鞋屈。 - 如果
reference_kind
的值是5
(REF_invokeVirtual
) 或者8
(REF_newInvokeSpecial
),那么reference_index
必須是常量池中CONSTANT_Methodref_info
類型常量的有效索引故觅,表示一個類中的實例方法或者構(gòu)造函數(shù)厂庇。 - 如果
reference_kind
的值是6
(REF_invokeStatic
)
或者7
(REF_invokeSpecial
),且class
版本小于52
(即JVM8
)输吏,那么reference_index
必須是CONSTANT_Methodref_info
類型索引权旷;如果 版本大于或者等于52
,那么reference_index
必須是CONSTANT_Methodref_info
類型索引或者CONSTANT_InterfaceMethodref_info
類型索引评也,因為JVM8
以上接口有默認(rèn)方法炼杖。 - 如果
reference_kind
的值是9
(REF_invokeInterface
),那么reference_index
必須是CONSTANT_InterfaceMethodref_info
類型索引盗迟。 - 如果
reference_kind
的值是5
(REF_invokeVirtual
),6
(REF_invokeStatic
),7
(REF_invokeSpecial
), or9
(REF_invokeInterface
),那么reference_index
必須是CONSTANT_Methodref_info
類型索引或者CONSTANT_InterfaceMethodref_info
類型索引熙含,但是不能是實例初始化方法<init>
和 類初始化方法<clinit>
罚缕。 - 如果
reference_kind
的值是8
(REF_newInvokeSpecial
),那么reference_index
必須是CONSTANT_Methodref_info
類型索引怎静,而且它就是實例初始化方法<init>
邮弹。
- 如果
4.9 CONSTANT_MethodType_info
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
-
tag
: 表示常量類型,CONSTANT_MethodType_info
的值就是16
(CONSTANT_MethodType
)蚓聘。 -
descriptor_index
: 表示方法的描述符腌乡。值必須是當(dāng)前常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引。
4.10 CONSTANT_InvokeDynamic_info
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
-
tag
: 表示常量類型夜牡,CONSTANT_InvokeDynamic_info
的值就是18
(CONSTANT_InvokeDynamic
)与纽。 -
bootstrap_method_attr_index
: 表示引導(dǎo)方法bootstrap_methods
中的有效索引。引導(dǎo)方法
bootstrap_methods
記錄在Class 文件
的BootstrapMethods
屬性中。 -
name_and_type_index
: 表示方法的名稱和描述符的索引急迂。值必須是當(dāng)前常量池中一個
CONSTANT_NameAndType_info
類型常量的有效索引影所。
五. 訪問標(biāo)志(access_flags
)
我們知道類,方法,字段都有不同的訪問標(biāo)志,在Class 文件
中使用一個u2
類型數(shù)據(jù)項來存儲僚碎,也就是最多可以有 16
個不同標(biāo)志位猴娩。
在類,方法,字段中有相同的標(biāo)志,也有不同的標(biāo)志勺阐,總體規(guī)劃卷中,我們可以借助 Modifier
類的源碼來了解:
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
static final int BRIDGE = 0x00000040;
static final int VARARGS = 0x00000080;
static final int SYNTHETIC = 0x00001000;
static final int ANNOTATION = 0x00002000;
static final int ENUM = 0x00004000;
static final int MANDATED = 0x00008000;
- 因為
access_flags
是兩個字節(jié)數(shù),這里使用int
類型渊抽,也就說前面4
個永遠是0
(0x0000----
)蟆豫。- 這里
0x00000040
和0x00000080
重復(fù)使用了,但是沒關(guān)系腰吟,因為表示不同的訪問標(biāo)志无埃。
5.1 類的訪問標(biāo)志
在 Modifier
類中,類的訪問標(biāo)志:
private static final int CLASS_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE |
Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL |
Modifier.STRICT;
private static final int INTERFACE_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE |
Modifier.ABSTRACT | Modifier.STATIC | Modifier.STRICT;
我們知道在 java
中類可以用的修飾符有: public
,protected
,private
,abstract
,static
,final
,strictfp
毛雇。
- 其中
protected
,private
和static
都是只能用在內(nèi)部類里面嫉称。strictfp
這個關(guān)鍵字表示這個類精確進行浮點運算。不過這么多年也沒有看誰用過灵疮。final
關(guān)鍵字不能用在接口上织阅。
但是我們再看 Class 文件
中類的訪問標(biāo)志:
標(biāo)志名 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 聲明為 public ,可以從包外訪問 |
ACC_FINAL | 0x0010 | 聲明為 final 震捣,不允許被繼承 |
ACC_SUPER | 0x0020 | 為了兼容之前編譯器編譯代碼而設(shè)置的荔棉,目前編譯器編譯的代碼,這個標(biāo)志位都是1 蒿赢。 |
ACC_INTERFACE | 0x0200 | 表示是類還是接口 |
ACC_ABSTRACT | 0x0400 | 聲明為 abstract 润樱,不能被實例化 |
ACC_SYNTHETIC | 0x1000 | 聲明為 synthetic ,表示這個 Class 文件 不在源代碼中 |
ACC_ANNOTATION | 0x2000 | 表示為注解類型 |
ACC_ENUM | 0x4000 | 表示為枚舉類型 |
仔細(xì)看羡棵,你會發(fā)現(xiàn)有些不同點:
- 內(nèi)部類中的三個修飾符信息壹若,沒有出現(xiàn)在
Class 文件
中類的訪問標(biāo)志里。的確是這樣皂冰,三個修飾符只會控制
javac
編譯行為的不同店展,編譯完的內(nèi)部類class
文件中,是沒有這三個修飾符信息秃流。例如赂蕴,非static
修飾的內(nèi)部類,javac
編譯的時候舶胀,會在class
文件中概说,添加一個字段碧注,類型就是外部類。 -
ACC_INTERFACE
,ACC_ANNOTATION
和ACC_ENUM
分別表示java
目前的三種類型interface
,@interface
和enum
席怪。
5.2 字段的訪問標(biāo)志
在 Modifier
類中应闯,字段的訪問標(biāo)志:
private static final int FIELD_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE |
Modifier.STATIC | Modifier.FINAL | Modifier.TRANSIENT |
Modifier.VOLATILE;
我們知道在 java
中字段可以用的修飾符有: public
,protected
,private
,static
,final
,transient
和 volatile
。
- 其中
transient
表示這個字段是瞬時挂捻,進行java
序列化的時候碉纺,不會序列化被transient
修飾的字段。volatile
表示這個字段是可見的刻撒。volatile
關(guān)鍵字的詳細(xì)介紹請看 Java多線程詳細(xì)介紹骨田。
但是我們再看 Class 文件
中字段的訪問標(biāo)志:
標(biāo)志名 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 聲明為 public ,可以從包外訪問 |
ACC_PRIVATE | 0x0002 | 聲明為 private 声怔,只能在定義該字段的類中訪問 |
ACC_PROTECTED | 0x0004 | 聲明為 protected 态贤,子類可以訪問 |
ACC_STATIC | 0x0008 | 聲明為 static
|
ACC_FINAL | 0x0010 | 聲明為 final ,表示對象構(gòu)造完成后醋火,不能直接修改該字段了 |
ACC_VOLATILE | 0x0040 | 聲明為 volatile
|
ACC_TRANSIENT | 0x0080 | 聲明為 transient
|
ACC_SYNTHETIC | 0x1000 | 表示該字段不是在源碼中悠汽,由編譯器生成 |
ACC_ENUM | 0x4000 | 表示該字段是枚舉(enum )類型 |
Class 文件
中字段的訪問標(biāo)志和java
中字段的修飾符差不多,只是多了 ACC_SYNTHETIC
和 ACC_ENUM
兩個標(biāo)志芥驳。
5.3 方法的訪問標(biāo)志
在 Modifier
類中柿冲,方法的訪問標(biāo)志:
private static final int CONSTRUCTOR_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
private static final int METHOD_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE |
Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL |
Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.STRICT;
我們知道在 java
中方法可以用的修飾符有:
public
,protected
,private
,abstract
,static
,final
,synchronized
, synchronized
和 strictfp
。
但是我們再看 Class 文件 中方法的訪問標(biāo)志:
標(biāo)志名 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 聲明為 public 兆旬,可以從包外訪問 |
ACC_PRIVATE | 0x0002 | 聲明為 private 假抄,只能在定義該字段的類中訪問 |
ACC_PROTECTED | 0x0004 | 聲明為 protected ,子類可以訪問 |
ACC_STATIC | 0x0008 | 聲明為 static
|
ACC_FINAL | 0x0010 | 聲明為 final , 表示方法不能被覆蓋 |
ACC_SYNCHRONIZED | 0x0020 | 聲明為 synchronized , 表示對方法的調(diào)用丽猬,會包裝在同步鎖里 |
ACC_BRIDGE | 0x0040 | 聲明為 bridge 宿饱,由編譯器產(chǎn)生 |
ACC_VARARGS | 0x0080 | 表示方法帶有變長參數(shù) |
ACC_NATIVE | 0x0100 | 聲明為 native , 不是由 java 語言實現(xiàn) |
ACC_ABSTRACT | 0x0400 | 聲明為 abstract ,該方法沒有實現(xiàn)方法 |
ACC_STRICT | 0x0800 | 聲明為 strictfp 脚祟,使用精確浮點模式 |
ACC_SYNTHETIC | 0x1000 | 該方法由編譯器合成的谬以,不是由源碼編譯出來的 |
六. 字段和方法
6.1 字段
字段詳情 field_info
的格式如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
-
access_flags
: 表示字段訪問標(biāo)志,在上面已經(jīng)介紹了由桌。 -
name_index
: 表示字段的名稱蛉签。值必須是常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引。 -
descriptor_index
: 表示字段的描述符。值必須是常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引。 -
attributes_count
: 表示當(dāng)前字段的屬性數(shù)量璧微。 -
attributes[attributes_count]
: 表示當(dāng)前字段的屬性詳細(xì)信息的表嗦玖。
6.2 方法
方法詳情 method_info
的格式如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
-
access_flags
: 表示方法訪問標(biāo)志,在上面已經(jīng)介紹了妈经。 -
name_index
: 表示方法的名稱淮野。值必須是常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引捧书。 -
descriptor_index
: 表示方法的描述符。值必須是常量池中一個
CONSTANT_Utf8_info
類型常量的有效索引骤星。 -
attributes_count
: 表示當(dāng)前方法的屬性數(shù)量经瓷。 -
attributes[attributes_count]
: 表示當(dāng)前方法的屬性詳細(xì)信息的表。
關(guān)于 Class 文件
中屬性相關(guān)信息洞难,我們再后面章節(jié)介紹舆吮。
七. 例子
我們可以通過 javap
的命令來閱讀 Class 文件
中相關(guān)信息。
7.1 最簡單的例子
package com.zhang.jvm.reflect.example;
public class T {
}
這個是最簡單的一個類队贱,沒有任何字段和方法色冀,只繼承Object
類,我們來看看它編譯后的字節(jié)碼信息柱嫌,通過javap -p -v T.class
的命令:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/T.class
Last modified 2021-12-6; size 288 bytes
MD5 checksum 2771c8258a6734d72812fca914966e07
Compiled from "T.java"
public class com.zhang.jvm.reflect.example.T
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#13 // java/lang/Object."<init>":()V
#2 = Class #14 // com/zhang/jvm/reflect/example/T
#3 = Class #15 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/zhang/jvm/reflect/example/T;
#11 = Utf8 SourceFile
#12 = Utf8 T.java
#13 = NameAndType #4:#5 // "<init>":()V
#14 = Utf8 com/zhang/jvm/reflect/example/T
#15 = Utf8 java/lang/Object
{
public com.zhang.jvm.reflect.example.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/example/T;
}
SourceFile: "T.java"
- 前面的
minor version: 0
之前的都是.class
文件本身的信息锋恬,這里魔數(shù)magic
信息被省略了。- 然后依次就是常量池信息
Constant pool
编丘,字段相關(guān)信息(因為T.class
沒有字段与学,所以這里沒有),方法相關(guān)信息({}
里面的嘉抓,就是T.class
默認(rèn)的構(gòu)造器方法<init>
)索守,最后類屬性相關(guān)信息(就是這里的SourceFile: "T.java"
)。this_class
,super_class
,interfaces
相關(guān)信息就在public class com.zhang.jvm.reflect.example.T
中掌眠,Object
父類就隱藏蕾盯。
我們重點關(guān)注常量池相關(guān)信息,會發(fā)現(xiàn)雖然T.class
很干凈蓝丙,但是也有15
個常量级遭,來我們依次分析:
-
#1
: 這是一個CONSTANT_Methodref_info
類型,值是java/lang/Object."<init>":()V
渺尘。很明顯它就是
Object
類構(gòu)造器方法的符號引用挫鸽,通過這個引用來調(diào)用Object
類構(gòu)造器方法。 -
#2
: 這是一個CONSTANT_Class_info
類型鸥跟,值是com/zhang/jvm/reflect/example/T
丢郊。就是本類的全限定名稱。
-
#3
: 這是一個CONSTANT_Class_info
類型医咨,值是java/lang/Object
枫匾。 -
#4
: 這是一個CONSTANT_Utf8_info
類型,值是<init>
拟淮。默認(rèn)構(gòu)造器方法的名稱干茉。
-
#5
: 這是一個CONSTANT_Utf8_info
類型,值是()V
很泊。默認(rèn)構(gòu)造器方法的描述符角虫。
-
#6
: 這是一個CONSTANT_Utf8_info
類型沾谓,值是Code
,是一個屬性名稱戳鹅。 -
#7
: 這是一個CONSTANT_Utf8_info
類型均驶,值是LineNumberTable
,是一個屬性名稱枫虏。 -
#8
: 這是一個CONSTANT_Utf8_info
類型妇穴,值是LocalVariableTable
,是一個屬性名稱模软。 -
#9
: 這是一個CONSTANT_Utf8_info
類型伟骨,值是this
。 -
#10
: 這是一個CONSTANT_Utf8_info
類型燃异,值是Lcom/zhang/jvm/reflect/example/T;
携狭。它是
T
類型的描述符。 -
#11
: 這是一個CONSTANT_Utf8_info
類型回俐,值是SourceFile
逛腿,是一個屬性名稱。 -
#12
: 這是一個CONSTANT_Utf8_info
類型仅颇,值是T.java
单默。 -
#13
: 這是一個CONSTANT_Utf8_info
類型,值是"<init>":()V
忘瓦。它是構(gòu)造器方法的描述符搁廓。
-
#14
: 這是一個CONSTANT_Utf8_info
類型,值是com/zhang/jvm/reflect/example/T
耕皮。就是本類的全限定名稱境蜕。
-
#14
: 這是一個CONSTANT_Utf8_info
類型,值是java/lang/Object
凌停。
7.2 有字段和方法的例子
package com.zhang.jvm.reflect.example;
public class T {
private String name;
public void test() {}
}
與之前的例子相比較粱年,多了一個字段和方法,那么得到的字節(jié)碼信息如下:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/T.class
Last modified 2021-12-6; size 388 bytes
MD5 checksum f97a6c1995036e9605c0121916c3d815
Compiled from "T.java"
public class com.zhang.jvm.reflect.example.T
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#16 // java/lang/Object."<init>":()V
#2 = Class #17 // com/zhang/jvm/reflect/example/T
#3 = Class #18 // java/lang/Object
#4 = Utf8 name
#5 = Utf8 Ljava/lang/String;
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/zhang/jvm/reflect/example/T;
#13 = Utf8 test
#14 = Utf8 SourceFile
#15 = Utf8 T.java
#16 = NameAndType #6:#7 // "<init>":()V
#17 = Utf8 com/zhang/jvm/reflect/example/T
#18 = Utf8 java/lang/Object
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
public com.zhang.jvm.reflect.example.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/example/T;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/zhang/jvm/reflect/example/T;
}
SourceFile: "T.java"
與之前的相比較罚拟,發(fā)現(xiàn)有三個變化:
- 常量池中多了三個常量台诗,都是
CONSTANT_Utf8_info
類型的,分別是name
,Ljava/lang/String;
和test
赐俗。- 多了一個字段
name
相關(guān)信息拉队。- 多了一個方法
test
相關(guān)信息。
但是你會發(fā)現(xiàn)常量池中怎么沒有這個字段name
的CONSTANT_Fieldref_info
類型的常量呢阻逮?
那是因為我們沒有使用這個字段氏仗。
package com.zhang.jvm.reflect.example;
public class T {
private String name;
public void test() {}
public void test1() {
name = "12";
test();
}
}
多寫了一個方法test1
來調(diào)用name
字段和 test
方法,那么得到的字節(jié)碼信息如下:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/T.class
Last modified 2021-12-6; size 499 bytes
MD5 checksum 2ddae70db9ea9f755f9312eb2b2f2d07
Compiled from "T.java"
public class com.zhang.jvm.reflect.example.T
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = String #21 // 12
#3 = Fieldref #5.#22 // com/zhang/jvm/reflect/example/T.name:Ljava/lang/String;
#4 = Methodref #5.#23 // com/zhang/jvm/reflect/example/T.test:()V
#5 = Class #24 // com/zhang/jvm/reflect/example/T
#6 = Class #25 // java/lang/Object
#7 = Utf8 name
#8 = Utf8 Ljava/lang/String;
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/zhang/jvm/reflect/example/T;
#16 = Utf8 test
#17 = Utf8 test1
#18 = Utf8 SourceFile
#19 = Utf8 T.java
#20 = NameAndType #9:#10 // "<init>":()V
#21 = Utf8 12
#22 = NameAndType #7:#8 // name:Ljava/lang/String;
#23 = NameAndType #16:#10 // test:()V
#24 = Utf8 com/zhang/jvm/reflect/example/T
#25 = Utf8 java/lang/Object
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
public com.zhang.jvm.reflect.example.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/example/T;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/zhang/jvm/reflect/example/T;
public void test1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #2 // String 12
3: putfield #3 // Field name:Ljava/lang/String;
6: aload_0
7: invokevirtual #4 // Method test:()V
10: return
LineNumberTable:
line 10: 0
line 11: 6
line 12: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/zhang/jvm/reflect/example/T;
}
SourceFile: "T.java"
與之前的相比較,發(fā)現(xiàn)如下變化:
- 常量池中多了七個常量皆尔,分別是字符串
"12"
對應(yīng)的兩個常量#2
和#21
; 字段name
對應(yīng)的兩個常量#3
和#22
;方法test
對應(yīng)的兩個常量#4
和#23
; 以及方法test1
的名稱常量#17
。- 多了一個方法
test1
相關(guān)信息币励。
7.3 繼承的例子
package com.zhang.jvm.reflect.example;
public class TParent {
public String name;
public void say(){}
}
package com.zhang.jvm.reflect.example;
public class T extends TParent {
public void test() {
name = "T";
say();
}
}
這里定義一個父類TParent
慷蠕,有一個公共字段name
和方法say
。子類T
繼承TParent
類食呻,并有一個方法test
調(diào)用父類的字段和方法流炕,來看T
的字節(jié)碼信息:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/T.class
Last modified 2021-12-6; size 452 bytes
MD5 checksum aeea52a9b2b166d588e1336dd0a4dcc1
Compiled from "T.java"
public class com.zhang.jvm.reflect.example.T extends com.zhang.jvm.reflect.example.TParent
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#17 // com/zhang/jvm/reflect/example/TParent."<init>":()V
#2 = String #18 // T
#3 = Fieldref #5.#19 // com/zhang/jvm/reflect/example/T.name:Ljava/lang/String;
#4 = Methodref #5.#20 // com/zhang/jvm/reflect/example/T.say:()V
#5 = Class #21 // com/zhang/jvm/reflect/example/T
#6 = Class #22 // com/zhang/jvm/reflect/example/TParent
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/zhang/jvm/reflect/example/T;
#14 = Utf8 test
#15 = Utf8 SourceFile
#16 = Utf8 T.java
#17 = NameAndType #7:#8 // "<init>":()V
#18 = Utf8 T
#19 = NameAndType #23:#24 // name:Ljava/lang/String;
#20 = NameAndType #25:#8 // say:()V
#21 = Utf8 com/zhang/jvm/reflect/example/T
#22 = Utf8 com/zhang/jvm/reflect/example/TParent
#23 = Utf8 name
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 say
{
public com.zhang.jvm.reflect.example.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method com/zhang/jvm/reflect/example/TParent."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/example/T;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #2 // String T
3: putfield #3 // Field name:Ljava/lang/String;
6: aload_0
7: invokevirtual #4 // Method say:()V
10: return
LineNumberTable:
line 8: 0
line 9: 6
line 10: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/zhang/jvm/reflect/example/T;
}
SourceFile: "T.java"
- 你會發(fā)現(xiàn)字段
name
在常量池中#3
(Fieldref
)和方法say
在常量池中#4
(Methodref
),它們所屬類都是T
,而不是TParent
仅胞。- 但是你又發(fā)現(xiàn)在
T
的字節(jié)碼文件中每辟,就沒有name
字段相關(guān)信息和say
方法相關(guān)信息。- 這個是沒有關(guān)系的干旧,因為只要父類中相關(guān)字段和方法訪問權(quán)限是可以的渠欺,那么子類找不到也會到父類去找的。
但是如果你就想調(diào)用父類的該怎么辦呢椎眯?
這個很好辦挠将,我們知道
java
中有super
關(guān)鍵字。
package com.zhang.jvm.reflect.example;
public class T extends TParent {
public void test() {
super.name = "T";
super.say();
}
}
再來看T
的字節(jié)碼信息:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/T.class
Last modified 2021-12-6; size 452 bytes
MD5 checksum 7d0901b392b0bfd74300cb87482ba183
Compiled from "T.java"
public class com.zhang.jvm.reflect.example.T extends com.zhang.jvm.reflect.example.TParent
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#17 // com/zhang/jvm/reflect/example/TParent."<init>":()V
#2 = String #18 // T
#3 = Fieldref #6.#19 // com/zhang/jvm/reflect/example/TParent.name:Ljava/lang/String;
#4 = Methodref #6.#20 // com/zhang/jvm/reflect/example/TParent.say:()V
#5 = Class #21 // com/zhang/jvm/reflect/example/T
#6 = Class #22 // com/zhang/jvm/reflect/example/TParent
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/zhang/jvm/reflect/example/T;
#14 = Utf8 test
#15 = Utf8 SourceFile
#16 = Utf8 T.java
#17 = NameAndType #7:#8 // "<init>":()V
#18 = Utf8 T
#19 = NameAndType #23:#24 // name:Ljava/lang/String;
#20 = NameAndType #25:#8 // say:()V
#21 = Utf8 com/zhang/jvm/reflect/example/T
#22 = Utf8 com/zhang/jvm/reflect/example/TParent
#23 = Utf8 name
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 say
{
public com.zhang.jvm.reflect.example.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method com/zhang/jvm/reflect/example/TParent."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/example/T;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #2 // String T
3: putfield #3 // Field com/zhang/jvm/reflect/example/TParent.name:Ljava/lang/String;
6: aload_0
7: invokespecial #4 // Method com/zhang/jvm/reflect/example/TParent.say:()V
10: return
LineNumberTable:
line 8: 0
line 9: 6
line 10: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/zhang/jvm/reflect/example/T;
}
SourceFile: "T.java"
- 我們發(fā)現(xiàn)字段
name
在常量池中#3
(Fieldref
)和方法say
在常量池中#4
(Methodref
)编整,它們所屬類都變成了TParent
舔稀。- 還有一點需要特別注意的,就是在
test
方法的指令集中掌测,調(diào)用say
方法的指令内贮,從invokevirtual
指令變成invokespecial
指令。
子類可以直接使用父類允許訪問權(quán)限的字段和方法汞斧,即使子類中沒有相關(guān)字段和方法夜郁,這個是繼承的功效。
但是我們知道面向?qū)ο笳Z言断箫,除了繼承的特性拂酣,還有一個多態(tài)特性。
- 多態(tài)就是根據(jù)運行時仲义,對象實際類型來調(diào)用對應(yīng)方法婶熬,而不是編譯時寫死的類型去調(diào)用,所謂的寫死類型就是常量池中
Methodref
類型常量中所屬的類型埃撵。- 我們知道方法是具有多態(tài)特性的赵颅,那么字段也有多態(tài)么。
package com.zhang.jvm.reflect.example;
public class TParent {
public String name = "TParent";
public void say(){
System.out.println("I am TParent");
}
}
package com.zhang.jvm.reflect.example;
public class T extends TParent {
public String name = "T";
public void say(){
System.out.println("I am T");
}
public static void main(String[] agrs) {
TParent tParent = new T();
T t = (T) tParent;
System.out.println("tParent.name: "+ tParent.name+"\nt.name: "+t.name);
tParent.say();
t.say();
}
}
運行結(jié)果:
tParent.name: TParent
t.name: T
I am T
I am T
你會發(fā)現(xiàn)即使運行期是同一個對象暂刘,但是字段name
得到結(jié)果是不一樣的饺谬,即字段是不能多態(tài)的;但是方法的確是按照運行期實際對象類型調(diào)用。下面看它的字節(jié)碼信息:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/T.class
Last modified 2021-12-6; size 1070 bytes
MD5 checksum 3490fbda3bf98c7fbd556ef4c0f5f3f4
Compiled from "T.java"
public class com.zhang.jvm.reflect.example.T extends com.zhang.jvm.reflect.example.TParent
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #18.#38 // com/zhang/jvm/reflect/example/TParent."<init>":()V
#2 = String #39 // T
#3 = Fieldref #7.#40 // com/zhang/jvm/reflect/example/T.name:Ljava/lang/String;
#4 = Fieldref #41.#42 // java/lang/System.out:Ljava/io/PrintStream;
#5 = String #43 // I am T
#6 = Methodref #44.#45 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #46 // com/zhang/jvm/reflect/example/T
#8 = Methodref #7.#38 // com/zhang/jvm/reflect/example/T."<init>":()V
#9 = Class #47 // java/lang/StringBuilder
#10 = Methodref #9.#38 // java/lang/StringBuilder."<init>":()V
#11 = String #48 // tParent.name:
#12 = Methodref #9.#49 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#13 = Fieldref #18.#40 // com/zhang/jvm/reflect/example/TParent.name:Ljava/lang/String;
#14 = String #50 // \nt.name:
#15 = Methodref #9.#51 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#16 = Methodref #18.#52 // com/zhang/jvm/reflect/example/TParent.say:()V
#17 = Methodref #7.#52 // com/zhang/jvm/reflect/example/T.say:()V
#18 = Class #53 // com/zhang/jvm/reflect/example/TParent
#19 = Utf8 name
#20 = Utf8 Ljava/lang/String;
#21 = Utf8 <init>
#22 = Utf8 ()V
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 LocalVariableTable
#26 = Utf8 this
#27 = Utf8 Lcom/zhang/jvm/reflect/example/T;
#28 = Utf8 say
#29 = Utf8 main
#30 = Utf8 ([Ljava/lang/String;)V
#31 = Utf8 agrs
#32 = Utf8 [Ljava/lang/String;
#33 = Utf8 tParent
#34 = Utf8 Lcom/zhang/jvm/reflect/example/TParent;
#35 = Utf8 t
#36 = Utf8 SourceFile
#37 = Utf8 T.java
#38 = NameAndType #21:#22 // "<init>":()V
#39 = Utf8 T
#40 = NameAndType #19:#20 // name:Ljava/lang/String;
#41 = Class #54 // java/lang/System
#42 = NameAndType #55:#56 // out:Ljava/io/PrintStream;
#43 = Utf8 I am T
#44 = Class #57 // java/io/PrintStream
#45 = NameAndType #58:#59 // println:(Ljava/lang/String;)V
#46 = Utf8 com/zhang/jvm/reflect/example/T
#47 = Utf8 java/lang/StringBuilder
#48 = Utf8 tParent.name:
#49 = NameAndType #60:#61 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#50 = Utf8 \nt.name:
#51 = NameAndType #62:#63 // toString:()Ljava/lang/String;
#52 = NameAndType #28:#22 // say:()V
#53 = Utf8 com/zhang/jvm/reflect/example/TParent
#54 = Utf8 java/lang/System
#55 = Utf8 out
#56 = Utf8 Ljava/io/PrintStream;
#57 = Utf8 java/io/PrintStream
#58 = Utf8 println
#59 = Utf8 (Ljava/lang/String;)V
#60 = Utf8 append
#61 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#62 = Utf8 toString
#63 = Utf8 ()Ljava/lang/String;
{
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
public com.zhang.jvm.reflect.example.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method com/zhang/jvm/reflect/example/TParent."<init>":()V
4: aload_0
5: ldc #2 // String T
7: putfield #3 // Field name:Ljava/lang/String;
10: return
LineNumberTable:
line 6: 0
line 7: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/zhang/jvm/reflect/example/T;
public void say();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String I am T
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 10: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/zhang/jvm/reflect/example/T;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #7 // class com/zhang/jvm/reflect/example/T
3: dup
4: invokespecial #8 // Method "<init>":()V
7: astore_1
8: aload_1
9: checkcast #7 // class com/zhang/jvm/reflect/example/T
12: astore_2
13: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
16: new #9 // class java/lang/StringBuilder
19: dup
20: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V
23: ldc #11 // String tParent.name:
25: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: aload_1
29: getfield #13 // Field com/zhang/jvm/reflect/example/TParent.name:Ljava/lang/String;
32: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: ldc #14 // String \nt.name:
37: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: aload_2
41: getfield #3 // Field name:Ljava/lang/String;
44: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
47: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
50: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
53: aload_1
54: invokevirtual #16 // Method com/zhang/jvm/reflect/example/TParent.say:()V
57: aload_2
58: invokevirtual #17 // Method say:()V
61: return
LineNumberTable:
line 12: 0
line 13: 8
line 15: 13
line 17: 53
line 18: 57
line 19: 61
LocalVariableTable:
Start Length Slot Name Signature
0 62 0 agrs [Ljava/lang/String;
8 54 1 tParent Lcom/zhang/jvm/reflect/example/TParent;
13 49 2 t Lcom/zhang/jvm/reflect/example/T;
}
SourceFile: "T.java"
這個字節(jié)碼信息比較多募寨,我們主要觀察 main
方法的指令集信息:
tParent.name
對應(yīng)第29
行指令族展,使用的常量池索引是#13
,是一個Fieldref
類型拔鹰,所屬的類索引就是TParent
仪缸。tParent.name
對應(yīng)第41
行指令,使用的常量池索引是#3
列肢,是一個Fieldref
類型恰画,所屬的類索引就是T
。tParent.say()
對應(yīng)第54
行指令瓷马,使用的常量池索引是#16
拴还,是一個Methodref
類型,所屬的類索引就是TParent
欧聘。tParent.say()
對應(yīng)第58
行指令片林,使用的常量池索引是#17
,是一個Methodref
類型树瞭,所屬的類索引就是T
拇厢。
主要的區(qū)別就是在于 invokevirtual
指令,就是它實現(xiàn)了方法多態(tài)的功能晒喷,它會根據(jù)運行期對象實際類型去匹配對應(yīng)的方法孝偎,而不是根據(jù)這里 Methodref
常量中規(guī)定所屬類去匹配。
7.4 內(nèi)部類
在 java
語言中內(nèi)部類其實是一個非常特殊的存在凉敲,里面有很多javac
編譯器幫我們做的事情衣盾,如下是一個簡單的內(nèi)部類:
public class TS {
TInner inner = null;
public void test() {
inner.name = null;
}
class TInner {
private String name;
}
}
先看一下TS
的字節(jié)碼:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/example/TS.class
Last modified 2021-12-6; size 637 bytes
MD5 checksum 5ebdb2d72b0b3d5a224c59860e8b386a
Compiled from "TS.java"
public class com.zhang.jvm.reflect.example.TS
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#22 // com/zhang/jvm/reflect/example/TS.inner:Lcom/zhang/jvm/reflect/example/TS$TInner;
#3 = Methodref #6.#23 // com/zhang/jvm/reflect/example/TS$TInner.access$002:(Lcom/zhang/jvm/reflect/example/TS$TInner;Ljava/lang/String;)Ljava/lang/String;
#4 = Class #24 // com/zhang/jvm/reflect/example/TS
#5 = Class #25 // java/lang/Object
#6 = Class #26 // com/zhang/jvm/reflect/example/TS$TInner
#7 = Utf8 TInner
#8 = Utf8 InnerClasses
#9 = Utf8 inner
#10 = Utf8 Lcom/zhang/jvm/reflect/example/TS$TInner;
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/zhang/jvm/reflect/example/TS;
#18 = Utf8 test
#19 = Utf8 SourceFile
#20 = Utf8 TS.java
#21 = NameAndType #11:#12 // "<init>":()V
#22 = NameAndType #9:#10 // inner:Lcom/zhang/jvm/reflect/example/TS$TInner;
#23 = NameAndType #27:#28 // access$002:(Lcom/zhang/jvm/reflect/example/TS$TInner;Ljava/lang/String;)Ljava/lang/String;
#24 = Utf8 com/zhang/jvm/reflect/example/TS
#25 = Utf8 java/lang/Object
#26 = Utf8 com/zhang/jvm/reflect/example/TS$TInner
#27 = Utf8 access$002
#28 = Utf8 (Lcom/zhang/jvm/reflect/example/TS$TInner;Ljava/lang/String;)Ljava/lang/String;
{
com.zhang.jvm.reflect.example.TS$TInner inner;
descriptor: Lcom/zhang/jvm/reflect/example/TS$TInner;
flags:
public com.zhang.jvm.reflect.example.TS();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aconst_null
6: putfield #2 // Field inner:Lcom/zhang/jvm/reflect/example/TS$TInner;
9: return
LineNumberTable:
line 6: 0
line 8: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/zhang/jvm/reflect/example/TS;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field inner:Lcom/zhang/jvm/reflect/example/TS$TInner;
4: aconst_null
5: invokestatic #3 // Method com/zhang/jvm/reflect/example/TS$TInner.access$002:(Lcom/zhang/jvm/reflect/example/TS$TInner;Ljava/lang/String;)Ljava/lang/String;
8: pop
9: return
LineNumberTable:
line 11: 0
line 12: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/zhang/jvm/reflect/example/TS;
}
SourceFile: "TS.java"
InnerClasses:
#7= #6 of #4; //TInner=class com/zhang/jvm/reflect/example/TS$TInner of class com/zhang/jvm/reflect/example/TS
在這個類的字節(jié)碼文件中,你會發(fā)現(xiàn)一個很有意思的事情爷抓,那就是在 test
方法指令集中势决,語句 inner.name = null
居然沒有對應(yīng) putfield
指令,而是 invokestatic
指令蓝撇,調(diào)用了TInner
中一個名為access$002
的類方法果复。
為什么會這樣呢?
- 那是因為字段都是有訪問權(quán)限的渤昌,而
TInner
中name
字段的訪問權(quán)限是private
虽抄,那么只有這個TInner
類里面才可以訪問它,也就是這個name
字段只能在TInner
類的常量池中生成對應(yīng)的CONSTANT_field_info
類型常量独柑。- 但是
java
語言規(guī)范里面迈窟,外部類又可以調(diào)用內(nèi)部類的私有字段,所以javac
編譯器幫我們做了處理忌栅,在TInner
類中生成了一個名為access$002
靜態(tài)方法來給私有字段賦值车酣。
TInner
的字節(jié)碼不能使用javap
命令看到,我就簡單地說一下,有如下重點:
-
TInner
字節(jié)碼中有兩個字段湖员,name
和this$0
贫悄。this$0
的類型就是Lcom/zhang/jvm/reflect/example/TS;
,也就是說內(nèi)部類都持有一個外部類類型的字段破衔。 -
TInner
字節(jié)碼的構(gòu)造器方法0 aload_0 1 aload_1 2 putfield #2 <com/zhang/jvm/reflect/example/TS$TInner.this$0> 5 aload_0 6 invokespecial #3 <java/lang/Object.<init>> 9 return
也就是說內(nèi)部類的構(gòu)造函數(shù)肯定會將外部類的引用傳遞進來的清女,然后賦值給
this$0
字段。 - 如果外部類給內(nèi)部類的私有變量賦值了晰筛,那么就會生成一個靜態(tài)方法,來進行內(nèi)部類私有變量的賦值拴袭。