BaiFan
文章目录
  1. 1. 2.1 结构
    1. 1.1. 2.1.1 概览
    2. 1.2. 2.1.2 内部名
    3. 1.3. 2.1.3 类型描述符
    4. 1.4. 2.1.4 方法描述符

ASM-类-结构

本章介绍了使用ASM core的API,生成编译后的class和转换编译后的class。
首先展示了编译后的class文件,然后介绍相应的ASM接口、组件和工具类来生成和转换这些class,并且附带了很多说明性的事例。
方法、注释、和Generics(TODO 需要找一个合理的翻译)会在接下来的章节介绍。

2.1 结构

2.1.1 概览

编译类的整体结构是十分简单的。事实上,不同于本地编译的应用程序,一个编译后的class保留了结构化的信息和源码中几乎所有的符号(symbol)引用。

事实上,一个编译的class包含以下部分:

  1. 描述修饰符的部分(例如public、private),类名、父类、实现的接口集合和类的注解(annotation)。
  2. class声明的属性部分。每一个部分都包括修饰符、属性名、属性类型和属性的注解。
  3. class声明的构造函数和方法部分。每一个部分都包括修饰符、方法名、返回类型、参数类型和方法的注解。它还包含了方法编译后的字节码信息,由一系列的字节码指令集组成。

源码和编译后的class之间有一些不同之处:

  1. 一个编译后的class仅仅描述一个类,然后一个源码文件可以包含几个类的声明。例如一个源码文件可以声明一个主类和一个该类的内部类,在编译后会成为两个class文件:一个表示该主类,另一个表示内部类。
    然而该主类文件中包含了对内部类的引用,并且内部类定义的内部方法包含了对他们封闭方法的引用。(TODO inner classes defined inside methods contain a reference to their enclosing method.)
  2. 一个编译后的class不包含注释,但是包含了类、属性、方法和代码这些元素所关联的附加属性。自从Java 5中引入了注解,起到了相同的作用后,附加属性就几乎不再被使用了。
  3. 一个编译后的class不包含package声明和import声明部分,因此所有的类型名称必须是全路径的。

另一个非常重要的结构不同是一个编译后的class包含了常量池部分。常量池是一个数组,包括该类中所有出现的数字型、字符串和类型的常量。
这些常量在常量池中只会被定义一次,在class文件的其他部分使用数组的索引来关联该常量值。
幸运的是,ASM隐藏了所有常量池相关的细节,因此你就不需要关心常量池了。表格2.1总结了编译后class的整体结构,确切的结构可以在Java虚拟机规范第四章中找到。

表格2.1 :编译后的class结构(*表示0个或者多个)

类结构
修饰符,类名,父类,接口
常量池:数值、字符串、类型常量
源文件名称(可选)
封闭的方法引用
注解*
Attribute*
内部类* 名称
类属性* 修饰符、名称、类型
注解
Attribute
方法* 修饰符、名称、返回值类型和参数类型
注解
Attribute
编译后的code

另一个重要的区别就是Java类型的表示方式在源码和编译后的class中不同。下一个部分将介绍它们如何在编译后的class中表示。

2.1.2 内部名

在许多情况下,类型被约束成一个类或者接口。
例如一个类的父类、一个类所实现的接口、被方法抛出的异常都不能是基础类型或者数组,一定是类或者接口。
这些类型在编译后class中使用内部名(internal name)表示。
这些内部名称是一个全路径类型,只不过分隔符由英文逗点换成了反斜线。
例如String类型的内部名是java/lang/String

2.1.3 类型描述符

内部名仅仅用于被约束成类或者接口的类型。在所有其他情况下,比如字段类型,Java中的类型在编译后的class中使用类型描述符表示。
类型描述符表格(表格 2.2)如下.

表格2.2 :Java类型描述符

Java类型 类型描述符
boolean Z
char C
byte B
short S
int I
float F
long J
double D
void V
Object Ljava/lang/Object;
int[] [I
Object[][] [[Ljava/lang/Object;

基础类型的描述符使用单个字符:Z表示boolean,C表示char,B表示byte,S表示short,I表示int,F表示float,J表示long,D表示double。
类的类型描述符就是内部名加上前缀L和后缀分号(;)。例如String的类型描述符是Ljava/lang/String;
数组类型的描述符就是前缀[加上数组元素的类型描述符。

2.1.4 方法描述符

一个方法描述符是由一系列类型描述符组成的一个字符串,包括方法的参数类型和返回值类型。
方法描述符使用小括号开始,小括号内部是方法入参的类型描述符按照顺序拼接的字符串,在加上返回值的类型描述符组成,返回值是void的时候,使用V
方法的描述符不包含方法名和参数名。

表格2.3 :一些方法描述符示例

源码中的方法 方法描述符
void m(int i, float f) (IF)V
int m(Object o) (Ljava/lang/Object;)I
int[] m(int i, String s) (ILjava/lang/String;)[I
Object m(int[] i) ([I)Ljava/lang/Object;

只要你理解了类型描述符的转换方法,也很容易理解方法描述符。例如(I)I描述的是一个入参是int类型,返回值是int的方法。

文章目录
  1. 1. 2.1 结构
    1. 1.1. 2.1.1 概览
    2. 1.2. 2.1.2 内部名
    3. 1.3. 2.1.3 类型描述符
    4. 1.4. 2.1.4 方法描述符