第一部分 核心API
2类
这一章解释了如何使用ASM核心API来生成和转换编译后的java类。首先,结合一些说明性的例子,解释编译后的java类的相关结构,以及与之对应的ASM接口、组件和其他用于生成和转换的工具。关于方法,注解和泛型等内容,将在下一章节介绍。
2.1结构
2.1.1概述
一个编译后的java类的结构还是比较简单的。与被编译为本地代码的应用程序不同的是,编译后的java类保留了结构信息以及几乎所有的在源代码中定义的符号名称。事实上,编译后的java类包含以下几部分:
- 一个用于描述修饰符(public或者private),类名,父类名称,所实现的接口名称以及类注解的段。
- 一个用于描述类中字段信息的段。每个这样的段中都描述了字段的修饰符,名称,类型以及与该字段相关的注解。
- 一个用于描述方法和构造方法的段。每个段描述了方法的名称,返回值,参数类型以及方法的注解。同时也包含了方法编译后的字节码序列。
然而,在源代码和编译后的代码之间,还是存在一些不同:
- 一个编译后的java类仅仅只描述一个类信息,但是一个java源文件可以包含几个java类。例如,一个源文件可以定义一个包含内部类的java类,而编译后将会成为两个类文件,其中一个是主要的java类,另外一个是内部类。在主要的类中包含了指向内部类的引用,同时在主要的java类的方法中定义的内部类也会包含一个指向该java方法的引用。
- 一个编译后的java类不包含注释,当然,可以包含与类、字段、方法或者代码相关的属性,而这些属性可以用来关联一些额外的信息。在java 5中引入了注解以后,这些注解也可以实现同样的目的,因此,这些属性就变得不那么重要了。
- 一个编译后的java类不包含package和import段,因此,在编译后的类中,所有的类型名称都必须使用全路径。
另外在结构上,两者还有一个非常重要的不同,那就是一个编译后的java类包含一个常量池段。这个常量池是一个数组,它包含了在类中定义的所有数字,字符串以及类型常量。这些常量只在常量池中定义一次,在类的其它地方都是通过它们的索引来引用。让人高兴的是,ASM隐藏了与常量池相关的细节,你就不必为此而烦恼了。图2.1总结了一个编译后的java类的整体结构。类的具体结构请参看java虚拟机规范第四段。
图表 2.编译后的java类整体结构(*表示0或者更多)
另外一个不同,就是java的类型在源代码和编译后的类中是不同的,下一部分将介绍它们的表现方式。
2.1.2内部名称
在很多情况下,一个类型限于一个java类或者结构表示的类型,例如,一个类的父类,一个类所实现的接口,一个方法所抛出的异常(不可能是基本类型)或者数组,这些都是类或者接口类型。这些类型在编译后的类中以内部名称表示。一个类的内部名称就是这个类的全路径名称,将包名中的点号替换为/。例如,String的内部名称为java/lang/String。
2.1.3类型描述符
内部名称仅用作一个类或者接口的类型,所有其他的,如字段类型,java基本类型都是以类型描述符来表示的,见图2.2
图2.2 类型描述符
基本类型的描述符:Z表示boolean,C表示char,B表示byte,I表示int,F表示float,J表示long,D表示double。一个类的描述符就是这个类的内部名称,在前面加上一个L,在后面加上一个分号即可。例如,String的类型描述符就是Ljava/lang/String.最后,一个数组的类型描述符就是一个中括号[后面跟上数组元素的类型描述符。
2.1.4方法描述符
一个方法描述符就是一个包含参数类型的描述符,以及方法返回类型描述符的字符串。一个方法描述符以一个左括号开始,然后跟上每个参数的描述符,然后是一个右括号,最后就是返回值的类型描述符,如果一个方法的返回值是void,那么返回值的类型描述符就是V(一个方法描述符不包含这个方法的名称以及参数的名称)。
图2.3方法描述符示例
一旦你知道了类型描述符如何工作,那么理解方法描述符很容易。例如,(I)I描述了这样一个方法,它有一个int类型的参数,以及一个int返回值。图2.3给出了几个方法描述符的例子。