# 2.1 结构

# 2.1.1 概述

已编译类的总体结构非常简单。实际上,与原生编译应用程序不同,已编译类中保留了来自源代码的结构信息和几乎所有符号。事实上,已编译类中包含如下各部分:

  • 专门一部分,描述类的修饰符(比如 public 和 private)、名字、超类、接口和注释。
  • 类中声明的每个字段各有一部分。每一部分描述一个字段的修饰符、名字、类型和注释。
  • 类中声明的每个方法及构造器各有一部分。每一部分描述一个方法的修饰符、名字、返回类型与参数类型、注释。它还以 Java 字节代码指令的形式,包含了该方法的已编译代码。

但在源文件类和已编译类之间还是有一些差异:

  • 一个已编译类仅描述一个类,而一个源文件中可以包含几个类。比如,一个源文件描述了一个类,这个类又有一个内部类,那这个源文件会被编译为两个类文件:主类和内部类各一个文件。但是,主类文件中包含对其内部类的引用,定义了内部方法的内层类会包含引用,引向其封装的方法。

  • 已编译类中当然不包含注释(comment),但可以包含类、字段、方法和代码属性,可以利用这些属性为相应元素关联更多信息。Java 5 中引入可用于同一目的的注释 (annotaion)以后,属性已经变得没有什么用处了。

  • 编译类中不包含 package 和 import 部分,因此,所有类型名字都必须是完全限定的。另一个非常重要的结构性差异是已编译类中包含常量池(constant pool)部分。这个也是一个数组,其中包含了在类中出现的所有数值、字符串和类型常量。这些常量仅在这个常量池部分中定义一次,然后可以利用其索引,在类文件中的所有其他各部分进行引用。幸好,ASM 隐藏了与常量池有关的所有细节,所以我们不用再为它操心了。图 2.1 中总结了一个已编译类的整体结构。其确切结构在《Java 虚拟机规范》第 4 节中描述。

    已编译类的整体结构(*表示零个或多个)
    修饰符、名字、超类、接口
    常量池:数值、字符串和类型常量
    源文件名(可选)
    封装的类引用
    注释*
    属性*
    内部类* - 名称
    字段* 修饰符、名字、类型
    注释*
    属性*
    方法* 修饰符、名字、返回类型与参数类型
    注释*
    属性*
    编译后的代码

另一个重要的差别是 Java 类型在已编译类和源文件类中的表示不同。后面几节将解释它们在已编译类中的表示。

# 2.1.2 内部名

在许多情况下,一种类型只能是类或接口类型。例如,一个类的超类、由一个类实现的接口, 或者由一个方法抛出的异常就不能是基元类型或数组类型,必须是类或接口类型。这些类型在已编译类中用内部名字表示。一个类的内部名就是这个类的完全限定名,其中的点号用斜线代替。例如,String 的内部名为 java/lang/String

# 2.1.3 类型描述符

内部名只能用于类或接口类型。所有其他 Java 类型,比如字段类型,在已编译类中都是用类型描述符表示的(见图 2.2)。

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

图 2‐2 一些Java 类型的类型描述符

基元类型的描述符是单个字符: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(方法描述符中不包含方法的名字或参数名)。

源文件中的方法声明 方法描述符
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;

图 2.3 方法描述符举例

一旦知道了类型描述符如何工作,方法描述符的理解就容易了。例如,(I)I 描述一个方法, 它接受一个 int 类型的参数,返回一个 int。图 2.3 给出了几个方法描述符示例。