下面我们接着为访问标志,类索引,父类索引,接口索引集合,字段集合,方法表集合
访问标志:
常量池结束后紧接着的两个字节代表访问标志,用来标识一些类或接口的访问信息,包括:这个Class是类还是接口;是否定义为public;是否定义为abstract;如果是类的话,是否被声明为final等。具体的标志位以及含义如下表:
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否是public |
ACC_FINAL |
0x0010 |
是否被声明为final,只有类可以设置 |
ACC_SUPER |
0x0020 |
是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真 |
ACC_INTERFACE |
0x0200 |
标识是一个接口 |
ACC_ABSTRACT |
0x0400 |
是否是abstract,对于接口和抽象类来说为真,其他类都为假 |
ACC_SYNITHETIC |
0x1000 |
标识这个类并非由用户代码产生 |
ACC_ANNOTATION |
0x2000 |
标识这是一个注解 |
ACC_ENUM |
0x4000 |
标识这是一个枚举类 |
类索引(2个字节)、父类索引(2个字节)与接口索引(2个字节接口数+接口)集合:
在访问标志access_flags后接下来就是类索引(this_class)和父类索引(super_class),这两个数据都是u2类型的,而接下来的接口索引集合是一个u2类型的集合,class文件由这三个数据项来确定类的继承关系。由于Java中是单继承,所以父类索引只有一个;但Java类可以实现多个接口,所以接口索引是一个集合。
类索引用来确定这个类的全限定名,这个全限定名就是说一个类的类名包含所有的包名,然后使用"/"代替"."。比如Object的全限定名是java.lang.Object。父类索引确定这个类的父类的全限定名,除了Object之外,所有的类都有父类,所以除了Object之外所有类的父类索引都不为0.接口索引集合存储了implements语句后面按照从左到右的顺序的接口。
类索引和父类索引都是一个索引,这个索引指向常量池中的CONSTANT_Class_info类型的常量。然后再CONSTANT_Class_info常量中的索引就可以找到常量池中类型为CONSTANT_Utf8_info的常量,而这个常量保存着类的全限定名。
字段表集合:
字段表用来描述接口或类中声明的变量。字段包括类级变量和实例级变量,但不包括方法内变量。所谓的类级变量就是静态变量,这个变量不属于这个类的任何实例,可以不用定义类实例就可以使用;实例级变量不是静态变量,是和类实例相关联的,需要定义类实例才能使用。
那么,声明一个变量需要哪些信息呢?有:字段的作用域(public、private和protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、是否可被序列化(transient修饰符)、字段的数据类型(基本类型、对象、数组)以及字段名称。包含的信息有点多,不过不需要的可以不写。这些信息中,各个修饰符可以用布尔值表示。而字段叫什么名字、字段被定义为什么类型数据都是无法固定的,只能用常量池中的常量来表示。下面是字段表的格式:
类型 |
名称 |
数量 |
U2 |
access_flags |
1 |
U2 |
name_index |
1 |
U2 |
descriptor_index |
1 |
U2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
其中的字段修饰符access_flags,和类中的access_flags类似,对于字段来说可以设置的标志位及含义如下:
标志名称 |
标志值 |
含义 |
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 |
ACC_SYNTHETIC |
0x1000 |
字段是否是由编译器自动产生的 |
ACC_ENUM |
0x4000 |
字段是否是enum |
显然,ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED只能选择一个,ACC_FINAL和ACC_VOLATILE不能同时选择。接口中的字段必须有ACC_PUBLIC、ACC_STATIC和ACC_FINAL标志,这是Java语言本身的规则决定的。
access_flags给出了字段中所有可以用布尔值表示的修饰符,剩下的信息就是字段的名字、变量类型等信息。access_flags后面的是name_index和descriptor_index,前者是字段名的常量池索引,后者是字段描述符的常量池索引。name_index可以描述字段的名字,descriptor_index可以描述字段的数据类型。不过,对于方法的描述符来说就要复杂一些,因为一个方法除了返回值类型,还有参数类型,而且参数的个数还不确定。根据描述符规则,这些类型都使用一个大写字母来表示,如下表:
标识字符 |
含义 |
标识字符 |
含义 |
B |
byte |
J |
long |
C |
char |
S |
short |
D |
double |
Z |
boolean |
F |
float |
V |
void |
I |
int |
L |
对象类型,如Ljava/lang/Object |
对于数组类型,每一个维度将使用一个前置的“[”字符来描述。比如定义一个java.lang.String[][]类型的二维数组,将记录为"[[Ljava/lang/String",一个double数组"double[]"将标记为"[D"。
当描述符用来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号"()"内。比如方法void inc()的描述符是:()V。方法java.lang.String toString()的描述符是:()Ljava/lang/String。方法int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)的描述符是:([CII[CIII)I。
descriptor_info后面是属性信息,这会在后面属性表集合中介绍。
方法表集合:
在字段表集合中介绍了字段的描述符和方法的描述符,对于理解方法表有很大帮助。class文件存储格式中对方法的描述和对字段的描述几乎相同,方法表的结构也和字段表相同,这里就不再列出。不过,方法表的访问标志和字段的不同,列出如下:
标识名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
方法是否是public |
ACC_PRIVATE |
0x0002 |
方法是否是private |
ACC_PUBLICPROTECTED |
0x0004 |
方法是否是protected |
ACC_STATIC |
0x0008 |
方法是否是static |
ACC_FINAL |
0x0010 |
方法是否是final |
ACC_SYNCHRONIZED |
0x0020 |
方法是否是synchronized |
ACC_BRIDGE |
0x0040 |
方法是否是由编译器产生的桥接方法 |
ACC_VARARGS |
0x0080 |
方法是否接受不定参数 |
ACC_NATIVE |
0x0100 |
方法是否是native |
ACC_ABSTRACT |
0x0400 |
方法是否是abstract |
ACC_STRICTFP |
0x0800 |
方法是否是strictfp |
ACC_SYNTHETIC |
0x1000 |
方法是否是由编译器自动产生的 |
下面我们来一个例子
JavaCode
public class Test{
public int a;
private String b;
private int add(int arg1,int arg2){
return arg1+arg2;
}
}
二机制解析
Javap解析
C:\Users\GH\Desktop>javap -verbose Test.class
Classfile /C:/Users/GH/Desktop/Test.class
Last modified 2018-8-8; size 287 bytes
MD5 checksum 430dad91948d95b5532953a32356174c
Compiled from "Test.java"
public class Test
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 // Test
#3 = Class #18 // java/lang/Object
#4 = Utf8 a
#5 = Utf8 I
#6 = Utf8 b
#7 = Utf8 Ljava/lang/String;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 add
#13 = Utf8 (II)I
#14 = Utf8 SourceFile
#15 = Utf8 Test.java
#16 = NameAndType #8:#9 // "<init>":()V
#17 = Utf8 Test
#18 = Utf8 java/lang/Object
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public Test();
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 1: 0
}
SourceFile: "Test.java"
C:\Users\GH\Desktop>