Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表结合(attributes)几项,见下表。这些数据项目的含义也非常类似,仅在访问标志和属性表集合的可选项中有所区别。
类型 名称 数量 类型 名称 数量 u2 access_flags 1 u2 attributes_count 1 u2 name_index 1 attribute_info attributes attributes_count u2 descriptor_index 1
因为volatile关键字和transient关键字不能修饰方法,所以方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。与之相对的,synchronized、native、strictfp和abstract关键字可以修饰方法,所以方法表的访问标志中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志。对于方法表,所有标志位及其取值可参考下表。
标志名称 标志值 含义 ACC_PUBLIC 0x0001 方法是否为public ACC_PRIVATE 0x0002 方法是否为private ACC_PROTECTED 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 方法是否由编译器自动产生的
方法的定义可以通过访问标志、名称索引、描述符索引表达清楚,但方法里面的代码去哪里了?方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目。
以下面代码的Class文件为例对方法表集合进行分析,如下图所示,方法表集合的入口地址为:0x00000101,第一个u2类型的数据(即是计数器容量)的值为0x0002,代表集合中有两个方法(这两个方法为编译器添加的实例构造器<int>和源码中的方法inc())。第一个方法的访问标志值为0x001,也就是说只有ACC_PUBLIC标志为真,名称索引值为0x0007,查常量池得方法名为“<init>”,描述符索引值为0x0008,对应常量为“( ) V”,属性表计数器attributes_count的值为0x0001就表示此方法的属性表集合有一项属性,属性名称索引为0x0009,对应常量为“Code”,说明此属性是方法的字节码描述。
public class TestClass {
private int m;
public int inc() {
return m + 1;
}
}
与字段表集合相对应的,如果父类方法在子类汇总没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类构造器“<clinit>”方法和实例构造器“<init>”方法。
在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里面是无法仅仅依靠返回值的不同来对一个已有方法进行重载的。但是在Class文件格式汇总,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中的。