在class文件中,有两种数据类型:无符号数和表,其他的都是数据了
无符号数:以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数;
表:相当于C中的结构体,表中可以含有若干个无符号数以及子表,表类型名一般用info为结尾,事实上,Class文件整体也是一个表(以下为Class文件的数据项构成):
【1】常量池中只要存放两大类常量:字面量(Literal)和符号引用(Symbolic References),字面量比较接近于Java语言层面上的常量概念,如文本字符串,声明为final的常量值等。而符号引用则属于编译层面的概念,包含以下三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
【2】标识这个Class是否为(class、接口、枚举、注解),是否为public,是否为(final、abstract)
【3】类索引,父类索引和接口索引确定了这个类的继承关系。类索引和父类索引都上一个索引,通过这个索引,我们可以在常量池中找到需要的类全限定名。
(具体的做法是:这个两个类索引会指向一个类型为CONSTANT_Class_info的类描述符常量,通过这个常量,可以找到定义在CONSTANT_Utf8_info类型中的全限定名字符串)
【4】在这个表中,需要描述字段的作用域(public、private等修饰符),是否为类变量(static),可变性(fianl),并发可见性(volatile),可否被序列化(transient)(以上为字段修饰符)
字段数据类型(基本数据类型,对象,数组),字段名称。
具体情况:
【4.1】描述符的作用是,用来描述字段的数据类型,方法的参数列表和返回值。每个基本数据类型(int、float等)和void类型都会使用一个大写字母表示:
而对象类型用字符L加对象的全限定名来表示
另外,对于数组类型而言,每一维用一个[来表示。对于方法而言,会将参数列表中的参数的描述符依次放到一对括号中,在括号的后面再接上这个方法返回值的描述符,举几个栗子:
一个char类型的变量, 描述符为C
一个二维String数组 String[][] arr, 描述符为“[[Ljava/lang/String”
一个方法 String func(int p,char[] k,char t) 描述符为:“(I[CC)Ljava/lang/String”
在字节码中,如果两个字段的描述符不一致,那么重名也是合法的。
【4.2】字段表的属性表
字段表中的属性项会描述以下几个属性:
- ConstantValue,final关键字定义的关键字
- Deprecated,被声明为deprecaated的字段
【5】方法表与字段表基本一致(因此就不重复列出方法表的结构了),与之有一点细微差别的就是access_flags,对于一个方法,需要的描述的修饰符为:可见性修饰符(public、private等),静态修饰符(static),可否被重写(final,abstract),是否为同步方法(synchronized),是否为本地方法(native),是否需要严格浮点运算(stricfp)
此外还有就是:方法是否接受不定参数,是否为编译器产生的桥接方法,是否由编译器自动产生;
方法表的属性表见【5.1】
【5.1】方法表的属性表
常用的有以下的几个属性:
- Code,代码编译成的字节码指令(同样也是一个表)
- Deprecated,被声明为deprecaated的方法
- Exceptions,方法抛出的异常
【6】类文件的属性表
- EnclosingMethod,仅当一个类为局部类或匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
- innerClasses,内部类列表