每个字段(field)都由field_info结构所定义。
在同一个class文件中,不会有两个字段同时具有相同的字段名和描述符(见4.3.2小节)。 field_info结构格式如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
field_info结构各项的说明如下:
表4-4 表示字段访问权限和属性的各个标志
标志名 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public,可以从包外访问 |
ACC_PRIVATE | 0x0002 | 声明为private,只能在定义该字段的类中访问 |
ACC_PROTECTED | 0x0004 | 声明为protected,子类可以访问 |
ACC_STATIC | 0x0008 | 声明为static |
ACC_FINAL | 0x0010 | 声明为final,对象构造好之后,就不能直接设置该字段了 (JLS § 17.5) |
ACC_VOLATILE | 0x0040 | 声明为volatile,被标识的字段无法缓存 |
ACC_TRANSIENT | 0x0080 | 声明为transient,被标识的字段不会为持久化对象管理器所写入或读取 |
ACC_SYNTHETIC | 0x1000 | 被表示的字段由编译器产生,而没有写源代码中 |
ACC_ENUM | 0x4000 | 该字段声明为某个枚举类型(enum)的成员 |
class文件中的字段可以设置多个如表4-4所示的标志。不过有些标志是互斥的,一个字段最多只能设置 ACC_PRIVATE,ACC_PROTECTED 和 ACC_PUBLIC ( JLS § 8.3.1 ) 3 个标志中的一个,也不能同时设置标志 ACC_FINAL 和 ACC_VOLATILE (JLS §8.3.1.4)。接口中的所有字段都具有 ACC_PUBLIC、ACC_STATIC 和 ACC_FINAL 标志,也可以设置 ACC_SYNTHETIC 标志,但是不能含有表4-4中的其他标志(JLS §9.3)。
如果字段带有 ACC_SYNTHETIC 标志,则说明这个字段不是由源码产生的,而是由编译器自动产生的。
如果字段带有 ACC_ENUM 标志,这说明这个字段是一个枚举类型的成员。
表4-4 中没有出现的 access_flags 标志,是为了将来扩充而预留的,在生成的 class文件中应设置成0,Java虚拟机实现也应该忽略它们。
所有方法(method),包括实例初始化方法以及类或接口初始化方法(见2.9节)在内, 都由 method_info 结构来定义。
在一个class文件中,不会有两个方法同时具有相同的方法名和描述符(见4.3.3小节)。
method_info 结构格式如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
method_info 结构各项的说明如下:
表4-5 表示方法访问权限及属性的各标志
标志名 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public,可以从包外访问 |
ACC_PRIVATE | 0x0002 | 声明为private,只能从定义该方法的类中访问 |
ACC_PROTECTED | 0x0004 | 声明为protected,子类可以访问 |
ACC_STATIC | 0x0008 | 声明为static |
ACC_FINAL | 0x0010 | 声明为final,不能被覆盖(见5.4.5小节) |
ACC_SYNCHRONIZED | 0x0020 | 声明为synchronized,对该方法的调用,将包装在同步锁(monitor)里 |
ACC_BRIDGE | 0x0040 | 声明为bridge方法,由编译器产生 |
ACC_VARARGS | 0x0080 | 表示方法带有变长参数 |
ACC_NATIVE | 0x0100 | 声明为native,该方法不是用Java语言实现的 |
ACC_ABSTRACT | 0x0400 | 声明为abstract,该方法没有实现代码 |
ACC_STRICT | 0x0800 | 声明为strictfp,使用FP-strict浮点模式 |
ACC_SYNTHETIC | 0x1000 | 该方法是由编译器合成的,而不是由源代码编译出来的 |
class文件中的方法可以设置多个如表4-5所示的标志,但是有些标志是互斥的:一 个方法只能设置ACC_PR工VATE、ACC_PROTECTED 和 ACC_PUBLIC 这3个标志中的一个(JLS § 8.4.3 )。
接口方法可以设置表4-5里面除 ACC_PROTECTED、ACC_FINAL、ACC_SYNCHRONIZED 及ACC_NATIVE 之外的标志(JLS §9.4)。如果class文件的版本号小于52.0,那么接口中的每个方法必须设置 ACC_PUBLIC 及 ACC_ABSTRACT 标志;如果class文件的版本号大于或等于52.0,那么接口中的每个方法,必须设置 ACC_PUBLIC 或 ACC_PRIVATE 标志中的一个。
如果一个方法被设置 ACC_ABSTRACT 标志,则这个方法不能被设置 ACC_ FINAL、ACC_NATIVE、ACC_PRIVATE、ACC_STATIC、ACC_STRICT 或 ACC_ SYNCHRONIZED 标志(JLS §8.4.3.1、JLS §8.4.3.3、JLS §8.4.3.4)。
实例初始化方法(见2.9节)只能被 ACC_PRIVATE、ACC_PROTECTED 和 ACC_ PUBLIC 中的一个标识;还可以设置 ACC_STRICT、ACC_VARARGS 和 ACC_ SYNTHETIC 标志,但是不能再设置表4-5中的其他标志了。
类或接口初始化方法(见2.9节)由Java虚拟机隐式自动调用,它们的 access_flags 项的值除了 ACC_STRICT 标志外,其他标志都将被忽略。
ACC_BRIDGE 标志用于说明这个方法是由Java编译器生成的桥接方法㊀。
㊀ 桥接方法是JDK 1.5引入泛型后,为了使Java的范型方法生成的字节码和1.5版本前的字节码相兼容,由
编译器自动生成的方法。一译者注
ACC_VARARGS 标志用于说明方法在源码层的参数列表是否变长。如果是变长的,则在编译时,必须把方法的ACC_VARARGS 标志置1,其余方法的 ACC_VARARGS 标志必须置0。
如果方法被 ACC_SYNTHETIC 标志标识,这说明这个方法是由编译器生成的并且不会在源码中出现,少量的例外情况将在4.7.8小节中提到。
表4-5 中没有出现的 access_flags 标志位,是给将来预留的。它们在生成的class文件中应设置成0,Java虚拟机实现应该忽略它们。
name_index
name_index 项的值必须是对常量池表的一个有效索引。常量池表在该索引处的成员必须是CONSTANT_Utf8_info (见4.4.7小节)结构,它要么表示一个特殊方法的名字(<init>或见2.9节),要么表示一个方法的有效非限定名 (见4.2.2小节)。
descriptor_index
descriptor_index 项的值必须是对常量池表的一个有效索引。常量池表在该索引处的成员必须是CONSTANT_Utf8_info(见447小节)结构,此结构表示一个有效的方法的描述符(见4.3.3小节)。
本规范在未来的某个版本中可能会要求:当 access_flags 项的 ACC_VARARGS 标
记被设置时,方法描述符中的最后一个参数描述符必须是数组类型。
attributes_count
attributes_count 的项的值表示当前方法的附加属性(见4.7节)的数量。
attributes [ ]
属性表的每个成员的值必须是attribute(见4.7节)结构。
一个方法可以有任意个关联属性。
由本规范所定义,且可以出现在 method_info 结构 attributes 表(属性表)里的各属性,列在表4-6中。
method_info 结构 attributes 表里的各项预定义属性,其规则在4.7节中给出。 method_info 结构 attributes 表里的各项非预定义(non-predefined)属性,其规则在4.7.1小节中给出。
属性(attribute)在class文件格式中的ClassFile(见4.1节)结构、field_ info(见4.5节)结构、method_info(见4.6节)结构 和 Code_attribute(见4.7.3小节)结构都有使用。
所有属性的通用格式如下:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
对于任意属性,attribute_name_index 必须是对当前class文件的常量池的有效16位无符号索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info(见4.4.7小节)结构,用以表示当前属性的名字。attribute_length 项的值给出了跟随其后的信息字节的长度,这个长度不包括 attribute_name_index 和 attribute_length 项的6个字节。
本规范预定义了23个属性。它们以3种排列形式印在3张表格中,为了查阅方便,现将这3张表格解说如下:
本规范提到的这些属性,也就是在class文件结构 attributes 表(属性表)里出现的这些预定义属性(predefined attribute),其名称是保留的,属性表中的自定义属性不能再使用这些名称。
凡是出现在 attributes 表中的预定义属性,本节都会专门用一个小节来指出其用法。 若是没有另行说明,那就意味着该属性可以在 attributes 表中出现任意次。
这些预定义属性,可以按照用途分为下面三组:
表4-6 class文件中预定义的属性(按讲解该属性的章节排序)
属性名 | 章节 | class文件 | Java SE |
---|---|---|---|
Constantvalue | 4.7.2小节 | 45.3 | 1.0.2 |
Code | 4.7.3小节 | 45.3 | 1.0.2 |
StackMapTable | 4.7.4小节 | 50.0 | 6 |
Exceptions | 4.7.5小节 | 45.3 | 1.0.2 |
InnerClasses | 4.7.6小节 | 45.3 | 1.1 |
EnclosingMethod | 4.7.7小节 | 49.0 | 5.0 |
Synthetic | 4.7.8小节 | 45.3 | 1.1 |
Signature | 4.7.9小节 | 49.0 | 5.0 |
SourceFile | 4.7.10小节 | 45.3 | 1.0.2 |
SourceDebugExtension | 4.7.11小节 | 49.0 | 5.0 |
LineNumberTable | 4.7.12小节 | 45.3 | 1.0.2 |
LocalVariableTable | 4.7.13小节 | 45.3 | 1.0.2 |
LocalVariableTypeTable | 4.7.14小节 | 49.0 | 5.0 |
Deprecated | 4.7.15小节 | 45.3 | 1.1 |
RuntimeVisibleAnnotations | 4.7.16小节 | 49.0 | 5.0 |
RuntimeInvisibleAnnotations | 4.7.17小节 | 49.0 | 5.0 |
RuntimeVisibleParameterAnnotations | 4.7.18小节 | 49.0 | 5.0 |
RuntimeInvisibleParameterAnnotations | 4.7.19小节 | 49.0 | 5.0 |
RuntimeVisibleTypeAnnotations | 4.7.20小节 | 52.0 | 8 |
RuntimelnvisibleTypeAnnotations | 4.7.21小节 | 52.0 | 8 |
AnnotationDefault | 4.7.22小节 | 49.0 | 5.0 |
BootstrapMethods | 4.7.23小节 | 51.0 | 7 |
MethodParameters | 4.7.24小节 | 52.0 | 8 |
表4-7 class文件中预定义的属性(按class文件版本排序)
属性名 | class文件 | Java SE | 章节 |
---|---|---|---|
Constantvalue | 45.3 | 1.0.2 | 4.7.2小节 |
Code | 45.3 | 1.0.2 | 4.7.3小节 |
Exceptions | 45.3 | 1.0.2 | 4.7.5小节 |
SourceFile | 45.3 | 1.0.2 | 4.7.10小节 |
LineNumberTable | 45.3 | 1.0.2 | 4.7.12小节 |
LocalVariableTable | 45.3 | 1.0.2 | 4.7.13小节 |
InnerClasses | 45.3 | 1.1 | 4.7.6小节 |
Synthetic | 45.3 | 1.1 | 4.7.8小节 |
Deprecated | 45.3 | 1.1 | 4.7.15小节 |
EnclosingMethod | 49.0 | 5.0 | 4.7.7小节 |
Signature | 49.0 | 5.0 | 4.7.9小节 |
SourceDebugExtension | 49.0 | 5.0 | 4.7.11小节 |
LocalVariableTypeTable | 49.0 | 5.0 | 4.7.14小节 |
RuntimeVisibleAnnotations | 49.0 | 5.0 | 4.7.16小节 |
RuntimeinvisibleAnnotations | 49.0 | 5.0 | 4.7.17小节 |
RuntimeVisibleParameterAnnotations | 49.0 | 5.0 | 4.7.18小节 |
RuntimeInvisibleParameterAnnotations | 49.0 | 5.0 | 4.7.19小节 |
AnnotationDefault | 49.0 | 5.0 | 4.7.22小节 |
StackMapTable | 50.0 | 6 | 4.7.4小节 |
BootstrapMethods | 51.0 | 7 | 4.7.23小节 |
RuntimeVisibleTypeAnnotations | 52.0 | 8 | 4.7.20小节 |
RuntimelnvisibleTypeAnnotations | 52.0 | 8 | 4.7.21小节 |
Methodparameters | 52.0 | 8 | 4.7.24小节 |
表4-8 class文件中预定义的属性(按属性应该出现的位置排序)
属性名 | 位置 | class文件 |
---|---|---|
SourceFile | ClassFile | 45.3 |
InnerClasses | ClassFile | 45.3 |
EnclosingMethod | ClassFile | 49.0 |
SourceDebugExtension | ClassFile | 49.0 |
BootstrapMethods | ClassFile | 51.0 |
Constantvalue | field_info | 45.3 |
Code | method_info | 45.3 |
Exceptions | method_info | 45.3 |
RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations | method_info | 49.0 |
AnnotationDefault | method_info | 49.0 |
MethodParameters | method_info | 52.0 |
Synthetic | ClassFile,field_info,method_info | 45.3 |
Deprecated | ClassFile,field_info,method_info | 45.3 |
Signature | ClassFile,field_info,method_info | 49.0 |
RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations | ClassFile,field_info,method_info | 49.0 |
LineNumberTable | Code | 45.3 |
LocalVariableTable | Code | 45.3 |
LocalVariableTypeTable | Code | 49.0 |
StackMapTable | Code | 50.0 |
RuntimeVisibleTypeAnnotations,RuntimeInvisibleTypeAnnotations | ClassFile,field_info,method_info,Code | 52.0 |
Java虚拟机规范允许编译器在class文件的class文件结构、field_info结构、 method_info结构以及Code属性的属性表中定义和发布新的属性。Java虚拟机实现允许识别并使用这些属性表中出现的新属性。但是,所有未在class文件规范中定义的属性,不能影响class文件的语义。Java虚拟机实现必须忽略它不能识别的自定义属性。
例如,编译器可以定义新的属性用于支持与特定发行者相关(vendor-specific)的调试, 而不影响其他Java虚拟机实现。因为Java虚拟机实现必须忽略它们不能识别的属性,所以与特定发行者相关的虚拟机实现所使用的class文件也可以被别的Java虚拟机实现使用, 即使这些class文件包含的附加调试信息不能被那些虚拟机实现所用。
Java虚拟机规范明确禁止Java虚拟机实现仅仅因为class文件包含新属性而抛出异常或以其他形式拒绝使用class文件。当然,如果class文件没有包含所需的属性,那么某些工具可能无法正确操作这个class文件。
当两个不同的属性使用了相同的属性名且长度也相同时,无论虚拟机识别其中哪一个,都会引起冲突。本规范定义之外的自定义属性,必须按照《Java语言规范(Java SE8版)》 (JLS § 6.1 )中所规定的包命名方式来命名。
本规范在未来的版本中可能会再增加一些预定义的属性。
Constantvalue属性是定长属性,位于field_info (见4.5节)结构的属性表中。 Constantvalue属性表示一个常量表达式(JLS § 15.28)的值,其用法如下:
如果该字段为静态字段(即field_info结构的access_flags项设置了 ACC_STATIC 标志),则说明这个field_info结构所表示的字段,将赋值为它的Constantvalue属性所表示的值,这个过程也是该字段所在类或接口初始化阶段(见5.5节)的一部分。这个过程发生在调用类或接口的类初始化方法(见2.9节)之前。
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
ConstantValue_attribute结构各项的说明如下:
表4-9 ConstantValue属性的类型
字段类型 | 项类型 |
---|---|
long | CONSTANT_Long |
float | CONSTANT_Float |
double | CONSTANT_Double |
int、short、char、byte、boolean | CONSTANT_Integer |
String | CONSTANT_String |
Code属性是变长属性,位于method_info(见4.6节)结构的属性表中。Code属性中包含某个方法、实例初始化方法、类或接口初始化方法(见2.9节)的Java虚拟机指令及相关辅助信息。
如果方法声明为 native 或者 abstract 方法,那么 method_info 结构的属性绝不能有Code属性。在其他情况下,method_info 必须有且只能有一个Code属性。
Code属性的格式如下:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table [ exception_table_length ];
u2 attributes_count;
attribute_info attributes [ attributes_count ];
}
Code_attribute 结构各项的说明如下:
Code属性可以关联任意多个属性。
由本规范所定义且可以出现在Code属性attributes表里的各属性列在表4-8里。
Code属性attributes表里的各项预定义属性,其规则在4.7节中给出。
Code属性attributes表里的各项非预定义(non-predehned)属性,其规则在 4.7.1小节中给出。
StackMapTable属性是变长属性,位于Code (见4.7.3小节)属性的属性表中。这个属性用在虚拟机的类型检查验证阶段(见4.10.1小节)。
Code属性的属性表(attributes表)里面最多可以包含1个StackMapTable属性。
在版本号大于或等于50.0的class文件中,如果方法的Code属性中没有附带 Stack-MapTable 属性,那就意味着它带有一个隐式的栈映射属性(implicit stack map attribute)。这个隐式的栈映射属性的作用等同于 number_of_entries 值为0的 StackMapTable 属性。
StackMapTable 属性的格式如下:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries [ number_of_entries ];
}
StackMapTable_attribute 结构各项的说明如下:
栈映射帧(stack map frame) 显式或隐式地指定了某个字节码偏移量(bytecode offset),用来表示该帧所针对的字节码位置,并且指定了此偏移量处的局部变量和操作数栈项(operand stack entry)所需的核查类型(verification type)。
entries表中的每个栈映射帧,其某些语义要依赖于它的前一个栈映射帧。方法的首个栈映射帧是隐式的,类型检查器(type checker,见4.10.1.6小节)会根据方法描述符来算出该帧。因此,stack_map_frame结构体中的 entries [0] 描述的是方法的下一个㊀栈映射帧。
㊀ 原文为second。如果把隐式的栈映射帧算作第0个,那么entries[0]描述的就是第1个栈映射帧;如
果把隐式的栈映射帧算作第1个,那么entries[0]描述的就是第2个栈映射帧。——译者注
因为使用偏移量的增量,而没有直接使用实际的字节码偏移量,所以我们可以保证栈
映射帧是按正确顺序存放的。此外,由于能通过公式 offset_delta + 1 来根据每
个显式帧(不包括方法的首个帧,那个帧是隐式的)算出下一个显式帧的偏移量,因此
可以避免重复。
核查类型(verification type)指出了一个或两个存储单元(location)的类型,而存储单元是指单个的局部变量或单个的操作数栈项。核查类型由可辨识的联合体(discriminated union) 来表示,此联合体名叫 verification_type_info,它包含长度为1个字节的标记(tag),用以指明当前使用联合体中的哪一项来表示核查类型,标记后面跟着0个或多个字节,用来给出与标记有关的更多信息。
union verification_type_info {
Top_variable_info;
Integer_variable_info;
Float_variable_info;
Long_variable_info;
Double_variable_info;
Null_variable_info;
UninitializedThis_variable_info;
Object_variable_info;
Uninitialized_variable_info;
}
如果核查类型针对的是局部变量表或操作数栈中的某一个存储单元,那么可以用 verification_type_info 联合体中的下面几项之一来表示:
每个帧都显式或隐式地指明一个 offset_delta (偏移量的增量)值,用于计算每个帧在运行时的实际字节码偏移量。帧的字节码偏移量计算方法为:前一帧的字节码偏移量加上 offset_delta 的值再加1,如果前一个帧是方法的初始帧(initial frame),那么这时候字节码偏移量就是offset_delta。
在Code属性的 code[ ] 数组中,如果偏移量 i 的位置是某条指令的起点,同时这个 Code 属性包含StackMapTable属性,而它的 entries [ ] 数组中也有一个适用于偏移量 i 的 stack_map_frame 结构,那我们就说这条指令拥有与之相对应的栈映射帧。
Top_variable_info {
u1 tag = ITEM_Top; /* 0 */
}
Integer_variable_info {
u1 tag = ITEM_Integer; /* 1 */
}
Float_variable_info {
u1 tag = ITEM_Float; /* 2 */
}
Null_variable_info {
u1 tag = ITEM_Null; /* 5 */
}
UninitializedThis_variable_info {
u1 tag = ITEM_UninitializedThis; /* 6 */
}
Object_variable_info {
u1 tag = ITEM_Object; /* 7 */
u2 cpool_index;
}
Uninitialized_variable_info {
u1 tag = ITEM_Uninitialized /* 8 */
u2 offset;
}
如果核查类型针对的是局部变量表或操作数栈中的某两个存储单元,那么可以用 verification_type_info 联合体中的下面几项之一来表示:
Long_variable_info {
u1 tag = ITEM_Long; /* 4 */
}
Double_variable_info {
u1 tag = ITEM_Double; /* 3 */
}
栈映射帧由名为 Stack_map_frame 的可辨识联合(discriminated union)来表示,该联合体包含长度为1字节的标记㊀**(tag),用以指明当前使用的是联合体中的哪一项,其后跟着0个或多个字节,用来给出与标记有关的更多信息**。
㊀ 为了强调该标记的作用,译文有时将其称为类型标记。——译者注
union stack_map_frame {
same_frame;
same_locals_l_stack_item_frame;
same_locals_l_stack_item_frame_extended;
chop_frame;
same_frame_extended;
append_frame;
full_frame;
}
类型标记用来表示栈映射帧的帧类型(frame type):
㊁ 此处描述的 stack、locals 是 StackMapTable 属性 entries 数组里某个 stack_map_frarme 中
的项,它们与运行时栈帧中的操作数栈、局部变量表有映射关系,但并非同一种东西。原文中它们将描述
为"stack” 和 "operand stack”、"locals” 和 "local variables",在本文中,指代属性项时
使用 locals [ ] 表、stack[ ] 表来表示,而提到运行时栈帧时,则会明确翻译为操作数栈、局部变量表,
也请读者根据上下文注意区分。——译者注
same_frame {
u1 frame_type = SAME; /* 0-63 */
}
same_locals_l_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */
verification_type_info stack [1];
}
same_locals_l_stack_item_frame_extended {
u1 frame_type =-SAME_LOCALS_l_STACK_ITEM_EXTENDED; /* 247 */
u2 offset_delta;
verification_type_info stack[1];
}
chop_frame {
u1 frame_type = CHOP; /* 248-250 */
u2 offset_delta;
}
same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
u2 offset_delta;
}
append_frame {
u1 frame_type = APPEND; /* 252-254 */
u2 offset_delta;
verification_type_info locals[ frame_type - 251 ];
}
locals [ ] 表中的第0项,表示首个附加局部变量的核查类型。如果 locals[M] 表示的是第N个局部变量,那么:
对于任意的索引 i,locals [ i ] 所表示的局部变量,其索引都不能大于此方法的局部变量表的最大索引值。
full_frame {
u1 frame_type = FULL_FRAME; /* 255 */
u2 offset_delta;
u2 number_of_locals;
verification_type_info locals[number_of_locals];
u2 number_of_stack_items;
verification_type_info stack[number_of_stack_iterns];
}
locals [ ] 表中的第0项表示0号局部变量的核查类型。如果 locals [M] 表示的是第N个局部变量,那么:
stack [ ] 表中的第0项表示操作数栈底部那一个元素的核查类型,stack [ ] 表中后续各项表示离栈顶较近的那些元素的核查类型。我们把操作数栈底部那一个元素称作0号(第0个)栈元素,并把操作数栈中后续各元素,分别称作1号(第1个)、2号(第 2个)栈元素,依此类推。如果 stack [M] 表示的是第N个栈元素,那么:
Exceptions属性是变长属性,位于method_info(见4.6节)结构的属性表中。
**Exceptions属性指出了一个方法可能抛出的受检异常(checked exception)**㊀。 一个method_info结构的属性表中最多只能有一个Exceptions属性。 Exceptions属性的格式如下:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table [ number_of_exceptions ];
}
㊀ 受检异常这一术语的定义,请参阅《Java语言规范》(JavaSE8版)11.1.1小节。一一译者注
Exceptions_attribute 结构各项的说明如下:
如果一个方法要抛出异常,必须至少满足下列三个条件之一:
这些要求并不在Java虚拟机中进行强制检查,它们只在编译时进行强制检查。
InnerClasses属性是变长属性,位于ClassFile (见4.1节)结构的属性表中。
本小节为了方便说明,特别定义一个表示类或接口的class格式为C。如果类或接口C的常量池中包含某个CONSTANT_Class_info成员,且这个成员所表示的类或接口不属于任何一个包,那么C的ClassFile结构的属性表中就必须含有且只能含有1个 InnerClasses 属性。
InnerClasses属性的格式如下:
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes{
u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
}
InnerClasses_attribute结构各项的说明如下:
如果类或接口的成员中又包含某些类或接口,那么它的常量池表(以及对应的InnerClasses属性)必须包含这些成员,即使那些类或接口没有被这个class使用过(JLS. § 13.1 )。
此外,常量池表中的每个嵌套类(nested class)㊀和嵌套接口 (nested interface)都必须引用其外围类(enclosing class),因此,这些嵌套类和嵌套接口的InnerClasses属性里面,既有其外围类的信息,也有其本身的嵌套类及嵌套接口的信息。
㊀ 在不致混淆的情况下,嵌套类和内部类这两个词可以互换。——译者注
class [ ] 数组中每个成员包含以下4项:
表4-10 嵌套类的访问权标志及属性标志
标志 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 该嵌套类型在源文件中标注为public,或默认就是public |
ACC_PRIVATE | 0x0002 | 该类型在源文件中标注为private |
ACC_PROTECTED | 0x0004 | 在源文件中标注为protected |
ACC_STATIC | 0x0008 | 在源文件中标注为static,或默认就是static |
ACC_FINAL | 0x0010 | 在源文件中标注为final |
ACC_INTERFACE | 0x0200 | 是源文件中定义的interface(接口) |
ACC_ABSTRACT | 0x0400 | 在源文件中标注为abstract,或默认就是abstract |
ACC_SYNTHETIC | 0x1000 | 声明为synthetic(合成),意味着源文件中没有这个类型 |
ACC_ANNOTATION | 0x2000 | 声明为annotation(注解)类型 |
ACC_ENUM | 0x4000 | 声明为enum(枚举)类型 |
所有表4-10中没有定义的 inner_class_access_flags 项,都是为未来使用而预留的。这些二进制位在生成的class文件中应设置为0,Java虚拟机实现应忽略它们。
如果class文件的版本号为51.0或更高,且属性表中又有InnerClasses属性,那么若InnerClasses属性的 classes [ ] 中某个 inner_name_index 项的值为0,则它对应的 outer_class_info_index 项的值也必须为0。
Oracle的Java虚拟机实现不会检查InnerClasses属性和该属性所引用的类或接口的class文件是否一致性。
EnclosingMethod属性是可选的定长属性,位于ClassFile (见4.1节)结构的属性表中。当且仅当class为局部类或者匿名类(JLS §14.3,JLS §15.9.5)时,才能具有 EnclosingMethod 属性。
ClassFile结构的属性表中,最多只能有一个EnclosingMethod属性。
EnclosingMethod属性格式如下:
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index;
u2 method_index;
}
EnclosingMethod_attribute 结构各项的说明如下:
特别需要说明的是,如果当前类在源代码中直接处于实例初始化器(instance initializer)、 静态初始化器(static initializer)、实例变量初始化器(instance variable initializer) 或类变量初始化器(class variable initializer)之中,那么method_index 必须为0。(前两种情况涉及局部类和匿名类,后两种情况只涉及声明在字段赋值(field assignment)语句右侧的匿名类。)
否则 method_index 项的值必须是对常量池表的一个有效索引,常量池表在该索引处的项必须是CONSTANT_NameAndType_info(见4.4.6小节)结构,表示由 class_index 属性引用的类的对应方法的方法名和方法类型。
Java编译器有责任保证:通过method_index所确定的方法,在语法上最接近那个包含 EnclosingMethod 属性的类。
Synthetic 属性是定长属性,位于ClassFile(见4.1节)、field_info(见4.5节)或 method_info(见4.6节)的属性表中。如果一个类成员没有在源文件中出现,则必须标记带有Synthetic属性,或者设置ACC_SYNTHETIC 标志。唯一的例外是某些与人工实现无关的、由编译器自动产生的方法,也就是指Java语言默认的实例初始化方法(无参数的默认构造器,见2.9节)、类初始化方法(见2.9节),以及Enum.values和Enum.valueOf 方法。
Synthetic属性是在JDK 1.1中为了支持内部类或接口而引入的。
Synthetic属性的格式如下:
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Synthetic_attribute 结构各项的说明如下:
Signature属性是可选的定长属性,位于ClassFile(见4.1节)、field_info(见4.5节)或 method_info(见4.6节)结构的属性表中。在Java语言中,任何类、接口、构造器方法或字段的声明如果包含了类型变量(type variable)或参数化类型(parameterized type),则Signature属性会为它记录泛型签名信息。这些类型的详情可参阅《Java语言规范》(Java SE 8 版)。
Signature属性的格式如下:
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}
Signature_attribute 结构各项的说明如下:
Oracle的Java虚拟机实现在加载类或链接类的过程中并不检查Signature属性。Java SE平台的某些类库能够查询类、接口、构造器、方法及字段的泛型签名,而Signature属性的正确性就是由这些类库来检查的。比方说,Class类的 getGenericSuperclass 方法及 java.lang.reflect.Executable 类的 toGenericString 方法,就可以查询泛型签名。
签名(Signature)用来编码以Java语言所写的声明,这些声明使用了Java虚拟机类型系统之外的类型。在只能访问class文件的情况下,签名有助于实现反射、调试及编译。
如果类、接口、构造器、方法或字段的声明使用了类型变量或参数化类型,那么Java编译器就必须为此生成签名。具体来说,Java编译器在这些情况下必须生成签名:
带有终止符号以 Identifier 的语法规则用来表示Java编译器为类型、字段、方法、形式参数、局部变量或类型变量所生成的名称。这种名称里不能包含 . ; [ /< > :
这七个ASCII字符中的任意一个字符(也就是说,既不能使用方法名(参见4.2.2小节)所禁止的字符,又不能使用冒号),但是可以包含不能出现在Java语言标识符中的字符(参见JLS §3.8)。
签名依赖于由非终止符号(nonterminal)所构成的体系,这些非终止符号,称为类型签名 (type signature):
JavaTypeSignature:
ReferenceTypeSignature
BaseType
为了便于查阅,本规范把4.3.2小节对于BaseType的定义列在下面:
BaseType: one of
B C D F I J S Z
ReferenceTypeSignature:
ClassTypeSignature
TypeVariableSignature
ArrayTypeSignature
ClassTypeSignature:
L [PackageSpecifier]
SimpleClassTypeSignature {ClassTypeSignatureSuffix};
PackageSpecifier:
Identifier / {PackageSpecifier}
SimpleClassTypeSignature:
Identifier [TypeArguments]
TypeArguments:
< TypeArgument {TypeArgument} >
TypeArgument:
[Wildcardindicator] ReferenceTypeSignature
*
Wildcardindicator:
+
-
ClassTypeSignatureSuffix:
.SimpleClassTypeSignature
TypeVariableSignature:
T Identifier;
ArrayTypeSignature:
[JavaTypeSignature
类签名(class signature)用来编码类声明的类型信息(这个类可能是泛型类)。它描述了该类的类型参数,如果该类有直接超类或直接超接口(超类或超接口可能是参数化了的类或接口),那么类签名还会列出它们。类型参数通过其名称来描述,名称后面跟有任意的类限制(class bound)或接口限制(interface bound)。
ClassSignature:
[TypeParameters] SuperclassSignature {SuperinterfaceSignature}
TypeParameters:
< TypeParameter {TypeParameter} >
TypeParameter:
Identifier ClassBound {InterfaceBound}
ClassBound:
:[ReferenceTypeSignature]
InterfaceBound:
:ReferenceTypeSignature
SuperclassSignature:
ClassTypeSignature
SuperinterfaceSignature:
ClassTypeSignature
方法签名(method signature)用来编码方法声明的类型信息(此方法可能是泛型方法)。 它描述了方法可能带有的类型参数、(可能已经参数化了的)形式参数类型、(可能已经参数化了的)返回类型,以及声明在方法throws子句中的异常类型。
MethodSignature:
[TypeParameters] ({JavaTypeSignature}) Result {ThrowsSignature}
Result:
JavaTypeSignature
VoidDescriptor
ThrowsSignature:
^ ClassTypeSignature
^ TypeVariableSignature
为了便于查阅,本规范把4.3.3小节对于VoidDescriptor的定义列在下面:
VoidDescriptor:
V
由于各种编译器在生成方法签名时所用的机理不同,因此某方法的方法签名,未必会与该方法的方法描述符(参见4.3.3小节)完全匹配。尤其是方法签名中形式参数类型的个数,可能比方法描述符中参数描述符的个数要少。
字段签名(field signature)用来编码字段声明、形式参数声明或局部变量声明中(可能已经参数化了)的类型。
FieldSignature:
ReferenceTypeSignature
SourceFile属性是可选的定长属性,位于ClassFile(见4.1节)结构的属性表中。
一个ClassFile结构的属性表中最多只能包含一个SourceFile属性。
SourceFile属性的格式如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
SourceFile_attribute结构各项的说明如下:
SourceDebugExtension属性是可选属性,位于ClassFile(见4.1节)结构的属性表中。
一个ClassFile结构的属性表中最多只能包含一个SourceDebugExtension属性。
SourceDebugExtension属性的格式如下:
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}
SourceDebugExtension_attribute 结构各项的说明如下:
需要注意的是,debug_extension [ ] 数组表示的字符串可以比String类的实例所能表示的字符串更长。
LineNumberTable属性是可选的变长属性,位于Code(见4.7.3小节)结构的属性表中。它被调试器用于确定源文件中由给定的行号所表示的内容,对应于Java虚拟机 code [ ] 数组中的哪一部分。
在Code属性的属性表中,LineNumberTable属性可以按照任意顺序出现。
在Code属性attributes表中,可以有不止一个LineNumberTable属性对应于源文件中的同一行。也就是说,多个LineNumberTable属性可以合起来表示源文件中的某行代码,属性与源文件的代码行之间不必有一一对应的关系。
LineNumberTable属性的格式如下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table [ line_number_table_length ];
}
LineNumberTable_attribute 结构各项的说明如下:
LocalVariableTable 属性是可选变长属性,位于Code (见4.7.3小节)属性的属性表中。调试器在执行方法的过程中可以用它来确定某个局部变量的值。
在Code属性的属性表中,多个LocalVariableTable属性可以按照任意顺序出现。
Code属性attributes表中的每个局部变量,最多只能有一个LocalVariableTable 属性。
LocalVariableTable属性的格式如下:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
LocalVariableTable_attribute 结构各项的说明如下:
㊀ 这种说法可以理解为:当程序执行到code数组的[start_pc, start_pc + length)范围内时, 该局部变量是有效的。——译者注
LocalVariableTypeTable属性是可选的变长属性,位于Code(见4.7.3小节)的属性表中。调试器在执行方法的过程中,可以用它来确定某个局部变量的值。
在Code属性的属性表中,多个LocalVariableTable属性可以按照任意顺序出现。Code属性attributes表中的每个局部变量最多只能有一个LocalVariableTable属性。
LocalVariableTypeTable 属性和 LocalVariableTable 属性(见4.7.13小节) 并不相同,LocalVariableTypeTable提供的是签名信息而不是描述符信息。这仅仅对使用类型变量或参数化类型来做其类型的变量有意义。这种变量会同时出现在 LocalVariableTable 属性和 LocalVariableTypeTable 属性中,其他的变量仅出现在 LocalVariableTable 表中。
LocalVariableTypeTable 属性的格式如下:
LocalVariableTypeTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_type_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
} local_variable_type_table [local_variable_type_table_length];
}
LocalVariableTypeTable_attribute 结构各项的说明如下:
Deprecated属性是可选定长属性,位于ClassFile(见4.1节)、field_info(见 4.5节)或 method_info(见4.6节)结构的属性表中。类、接口、方法或字段都可以带有Deprecated属性。如果类、接口、方法或字段标记了此属性,则说明它将会在后续某个版本中被取代。
在运行时解释器或工具(比如编译器)读取class文件格式时,可以用Deprecated属性来告诉使用者避免使用这些类、接口、方法或字段,选择其他更好的方式。Deprecated属性的出现不会修改类或接口的语义。
Deprecated属性的格式如下:
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Deprecated_attribute 结构各项的说明如下:
RuntimeVisibleAnnotations 属性是变长属性,位于ClassFile(见4.1节)、field_info(见4.5节)或 method_info(见4.6节)结构的属性表中。RuntimeVisibleAnnotations 属性记录了添加在类声明、字段声明或方法声明上面㊀,且于运行时可见的注解。Java虚拟机必须令这些注解可供取用,以便使某些合适的反射API能够把它们返回给调用者。
㊀ 这里的上面只是一种泛称。有些注解可以添加在待注元素的左侧。具体位置请参阅《Java语言规范》中的
相关内容。——译者注
ClassFile、field_info 或 method_info 结构的属性表中,最多只能有一个RuntimeVisibleAnnotations属性。
RuntimeVisibleAnnotations 属性的格式如下:
RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeVisibleAnnotations_attribute 结构各项的说明如下:
annotation结构的格式如下:
annotation {
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs [num_e1ement_va1ue_pairs];
}
annotation 结构各项的说明如下:
㊀ 注解中的元素也称为配置参数或属性(attribute), 它的名称对应于键值对(element value pair)里的键
(element_name_index);它的值对应于键值对里的值(value)。————译者注
element_value 结构
element_value 结构是一个可辨识联合体(discriminated union)㊁用于表示 “元素-值” 的键值对中的值。
㊁ “discriminated union” 是一种数据结构,用于表示若干种具有独立特征的同类项集合。————译者注
element_value 结构的格式如下:
element_value {
u1 tag;
union {
u2 const_value_index;
{ u2 type_name_index;
u2 const_name_index;
} enum_const_value;
u2 class_info_index;
annotation annotation_value;
{ u2 num_values;
element_value values [num_values];
} array_value;
} value;
}
tag 项使用一个ASCII字符来表示键值对中的值是什么类型。这个tag决定了键值对中值的格式与value联合体里的哪一项相符。表4-11列出了 tag项的每一种有效字符,还列出了每个字符所表示的值类型,以及value联合体中与该字符相对应的项。在讲解value联合体中的每个项目时,我们会在该项下方的描述信息中用到表格的第4列。
表4-11 tag值的含义及其所表示的类型
tag项 | 类型 | value联合体中的项 | 常量类型 |
---|---|---|---|
B | byte | const_value_index | CONSTANT_Integer |
C | char | const_value_index | CONSTANT_Integer |
D | double | const_value_index | CONSTANT_Double |
F | float | const_value_index | CONSTANT_Float |
I | int | const_value_index | CONSTANT_Integer |
J | long | const_value_index | CONSTANT_Long |
S | short | const_value_index | CONSTANT_Integer |
Z | boolean | const_value_index | CONSTANT_Integer |
s | String | const_value_index | CONSTANT_Utf8 |
e | 枚举类型 | enum_const_value | 不适用 |
c | Class | class_info_index | 不适用 |
@ | 注解类型 | annotation_value | 不适用 |
[ | 数组类型 | array_value | 不适用 |
value项表示键值对中的值。此值是个联合体,它里面的各项如下:
Ljava/lang/Object;
,而类字面量 int.class 对应于类型int,所以常量池项就是I
。类字面量void.class对应于void,因此常量池项是V
,而类字面量Void.class对应于类型Void,所以常量池项是Ljava/lang/Void;
。RuntimelnvisibleAnnotations 属性是变长属性,位于ClassFile(见4.1节)、field_info(见4.5节)或method_info (见4.6节)结构的属性表中。它用于保存Java 语言中标注在类、方法或字段声明上面的运行时非可见注解。
每个ClassFile、field_info和method_info结构的属性表中最多只能含有一个 RuntimeInvisibleAnnotations 属性。
RuntimelnvisibleAnnotations 属性和 RuntimeVisibleAnnotations 属性(见4.7.16小节)相似,但不同的是,RuntimelnvisibleAnnotations表示的注解不能被反射API访问,除非Java虚拟机通过与实现相关的特殊方式(比如特定的命令行标志参数)保留这些注解。否则,Java虚拟机将忽略RuntimelnvisibleAnnotations属性。
RuntimelnvisibleAnnotations 属性的格式如下:
RuntimelnvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeInvisibleAnnotations_attribute 结构各项的说明如下:
RuntimeVisibleParameterAnnotations 属性是变长属性,位于 method_info(见4.6节)结构的属性表中。RuntimeVisibleParameterAnnotations 属性记录了标注在对应方法的形式参数声明上面的运行时可见注解。Java虚拟机必须保证这些注解可供取用,以便令合适的反射API能够将它们返回给调用者。
每个method_info结构的属性表中最多只能包含一个RuntimeVisibleParameterAnnotations属性。
RuntimeVisibleParameterAnnotations 属性的格式如下:
RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
ul num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
RuntimeVisibleParameterAnnotations_attribute 结构各项的说明如下:
RuntimelnvisibleParameterAnnotations 属性是变长属性,位于method_info(见4.6节)结构的属性表中。它用于保存标注在对应方法的形式参数声明上面的运行时不可见注解。
每个method_info结构的属性表中最多只能含有一个RuntimelnvisibleParameterAnnotations 属性。
RuntimelnvisibleParameterAnnotations属性和RuntimeVisibleParameterAnnotations属性(见4.7.18小节)类似,区别是RuntimelnvisibleParameterAnnotations属性表示的注解不能被反射的API访问,除非Java虚拟机通过与实现相关的特殊方式(比如特定的命令行标志参数)保留这些注解。否则,Java虚拟机将忽略RuntimelnvisibleParameterAnnotations属性。
RuntimelnvisibleParameterAnnotations 属性的格式如下:
RuntimelnvisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations [num_parameters];
}
RuntimeInvisibleParameterAnnotations_attribute 结构各项的说明如下:
RuntimeVisibleTypeAnnotations属性是ClassFile(见4.1节)、field_info(见4.5节)、method_info(见4.6节)结构或 Code 属性(见4.7.3小节)attributes 表中的变长属性。 RuntimeVisibleTypeAnnotations属性记录了标注在对应类声明、字段声明或方法声明所使用的类型上面的运行时可见注解,也记录了标注在对应方法体中某个表达式所使用的类型上面的运行时可见注解。此外,它还记录了标注在泛型类、接口、方法及构造器的类型参数声明上面的运行时可见注解。Java虚拟机必须使这些注解可供取用,以便令合适的反射API能够将它们返回给调用者。
在ClassFile结构、field_info结构、method_info结构或Code属性的属性表中,最多只能有1个RuntimeVisibleTypeAnnotations 属性。
只有当某个属性表的上级结构体或上级属性所对应的声明或表达式中带有加了注解的类型时,该属性表才可以包含RuntimeVisibleTypeAnnotations属性。
例如,对某个类声明的implements子句里的各类型所加的全部注解,都会记录在该类ClassFile结构体的 RuntimeVisibleTypeAnnotations 属 性 里。而对某个字段声明中的类型所加的全部注解,则会记录在该字段field_info结构体的 RuntimeVisibleTypeAnnotations 属性里。
RuntimeVisibleTypeAnnotations 属性的格式如下:
RuntimeVisibleTypeAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
type_annotation annotations[num_annotations];
}
RuntimeVisibleTypeAnnotations_attribute 结构体的各项,其含义如下:
type_annotation结构体的格式如下:
type_annotation {
u1 target_type;
union { .
type_parameter_target;
supertype_target;
type_parameter_bound_target;
empty_target;
method_formal_parameter_target;
throws_target;
localvar_target;
catch_target;
offset_target;
type_argument_target;
} target_info;
type_path target_path;
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs[num_element_value_pairs];
}
type_annotation 的前 3 项,也就是 target_type、target_info及target_ path,指出了带注解的类型所在的精确位置。而后3项,也就是type_index、num_element_value_pairs、element_value_pairs [ ],则指出了注解本身的类型及键值对。
type_annotation 结构各项的含义如下:
表4-12与表4-13列出了target_type的合法取值。每个值都是长度为1个字节的标记,用来指出当前使用的是target_type之后那个target_info联合体里的哪一项。联合体中正在使用的那一项给出了与注解目标有关的详细信息。
表4-12与表4-13中的每一种目标,对应于JLS §4.11里的一个类型语境。也就是说,值在0x10 ~ 0x17及0x40 ~ 0x42之间的target_type,对应于1 ~ 10号目标语境,而值在0x43 ~ 0x4B之间的target_type,则对应于11至15号类型语境。
target_type 项决定了 RuntimeVisibleTypeAnnotations 属性中的 type_ annotation 结构应该出现在 ClassFile 结构、field_info 结构、method_ info 结构还是Code属性中。表4-14指出了每个合法的target_type值所在的 type_annotation 结构体应该出现在谁的 RuntimeVisibleTypeAnnotations 属性之中。
表4-12 target_type值的含义(第1部分)
值 | 目标的种类 | target_info 项 |
---|---|---|
0x00 | 泛型类或接口的类型参数声明 | type_parameter_target |
0x01 | 泛型方法或构造器的类型参数声明 | type_parameter_target |
0x10 | 类或接口声明(也包括匿名类声明中的直接超类声明)中的extends子句里的类型,或者接口声明中的implements子句里的类型 | supertype_target |
0x11 | 在声明泛型类或接口的类型参数界限时,所用到的类型 | type_parameter_bound_target |
0x12 | 在声明泛型方法或构造器的类型参数界限时,所用到的类型 | type_parameter_bound_target |
0x13 | 字段声明中的类型 | empty_target |
0x14 | 方法的返回值类型,或者新构建好的对象的类型 | empty_target |
0x15 | 方法或构造器的接收者(receiver)类型 | empty_target |
0x16 | 方法、构造器或lambda表达式的形式参数声明中的类型 | formal_parameter_target |
0x17 | 方法或构造器throws子句中的类型 | throws_target |
表4-13 target_type值的含义(第2部分)
值 | 目标的种类 | target_info 项 |
---|---|---|
0x40 | 局部变量声明中的类型 | localvar_target |
0x41 | 资源变量声明中的类型 | localvar_target |
0x42 | 异常参数声明中的类型 | catch_target |
0x43 | instanceof 表达式中的类型 | offset_target |
0x44 | new 表达式中的类型 | offset_target |
0x45 | 以 ::new 的形式来表述的方法引用表达式中的类型 | offset_target |
0x46 | 以 ::Identifier 的形式来表述的方法引用表达式中的类型 | offset_target |
0x47 | 类型转换表达式中的类型 | type_argument_target |
0x48 | 表达式中的泛型构造器或显式构造器调用语句中的类型参数 | type_argument_target |
0x49 | 方法调用表达式中的泛型方法的类型参数 | type_argument_target |
0x4A | 在以 ::new的形式来表述的方法引用表达式中,泛型构造器的类型参数 | type_argument_target |
0x4B | 在以 ::Identifier的形式来表述的方法引用表达式中,泛型方法的类型参数 | type_argument_target |
表 4-14 target_type值所在的 RuntimeVisibleTypeAnnotations 属性应该出现的位置
值 | 目标的种类 | 位置 |
---|---|---|
0x00 | 泛型类或接口的类型参数声明 | ClassFile |
0x01 | 泛型方法或构造器的类型参数声明 | method_info |
0x10 | 类或接口声明中的extends子句里的类型,或者接口声明中的 implements子句里的类型 | ClassFile |
0x11 | 在声明泛型类或接口的类型参数界限时,所用到的类型 | ClassFile |
0x12 | 在声明泛型方法或构造器的类型参数界限时,所用到的类型 | method_info |
0x13 | 字段声明中的类型 | field_info |
0x14 | 方法或构造器的返回值类型 | method_info |
0x15 | 方法或构造器的接收者类型 | method_info |
0x16 | 方法、构造器或lambda表达式的形式参数声明中的类型 | method_info |
0x17 | 方法或构造器throws子句中的类型 | method_info |
0x40-0x4B | 局部变量声明、资源变量声明、异常参数声明及表达式中所用到的类型 | Code |
target_info联合体中的项精确地指出了本条注解添加在声明或表达式中的哪个类型上面。然而第一项却例外,它不是指出声明或表达式中的某个类型,而是指出哪一个类型参数的声明上面添加了注解。
联合体中各项的含义如下:
type_parameter_target {
u1 type_parameter_index;
}
type_parameter_index项的值指出了本条注解添加在哪个类型参数的声明上面。 它的值如果0,那就表示本条注解添加在首个类型参数声明上面。
supertype_target {
u2 supertype_index;
}
super type_index的值如果是65535,那就表示本条注解添加在类声明的 extends子句中的那个超类名称上。
除了65535之外的其他supertype_index值,都是对外围ClassFile结构 interfaces 数组的索引,该索引处的元素要么是类声明的 implements 子句中的某个超接口,要么是接口声明的extends子句中的某个超接口,本条注解就添加在那个超接口上面。
type_parameter_bound_target {
u1 type_parameter_index;
u1 bound_index;
}
type_parameter_index 项的值指出了带有注解的那个界限是针对哪一个类型参数声明而言的。type_parameter_index的值如果是0,那就表示该界限针对的是首个类型参数声明。
bound_index 项的值指出了在由 type_parameter_index 所确定的那个类型参数声明中,究竟是哪一个界限上面添加了本条注解。bound_index 的值如果是0,那就表示本注解添加在类型参数声明中的首个界限上面。
type_parameter_bound_target 项可以说明某个界限添加了注解,但它并没有记录该界限所指的类型。此类型可以从保存在适当的Signature属性中的类签名或方法签名里面查出。
empty_target {
}
由于这些位置上面都只会出现一个类型,所以当 target_info 联合体表示的是 empty_target 时,不需要再给出类型语境中每个类型的信息。
formal_parameter_target {
u1 formal_parameter_index;
}
formal_parameter_index 项的值指出了带有注解的类型位于哪一个形式参数声明之中。如果formal_parameter_index的值是0,那就表示该类型处在首个形式参数的声明之中。
formal_parameter_target 项可以说明某个形式参数的类型是带有注解的,但它并没有记录那个类型本身是什么。我们可以通过RuntimeVisibleTypeAnnotations属性外围的method_info结构中的方法描述符(见4.3.3小节)来检视该类型。 formal_parameter_index的值如果是0,那就表示该类型由方法描述符中的首个参数描述符来描述。
throws_target {
u2 throws_type_index;
}
throws_type_index 项的值是对 exception_index_table 数组的索引,该数组位于RuntimeVisibleTypeAnnotations 属性外围的 method_info 结构中的Exceptions属性里。
localvar_target {
u2 table_length;
{
u2 start_pc;
u2 length;
u2 index;
} table[table_length];
}
table_length 项的值给出了table数组中的元素个数。table数组的每个元素都以字节码在code数组中的偏移量来限定某个范围,此局部变量在这个范围内是有值的。数组元素还指出了此局部变量在当前帧的局部变量表中的索引。table数组的每个元素含有下列3项:
start_pc 和 length
当给定的局部变量位于code数组的 [start_pc,start_pc + length )范围内,也就是处在由偏移量大于等于 start_pc 且小于 start_pc + length 的字节码所构成的范围内时,该局部变量是有值的。
index
在当前帧的局部变量表中,给定的局部变量必定位于索引为index的位置上。
如果index所指的局部变量是double型或long型,那么该局部变量会占据 index 及 index + 1 这两个位置。
一个局部变量是可以有多个活跃范围(live range)的,而这些范围可以用局部变量表中的多个索引项来表示㊀,因此,其类型添加了注解的局部变量,必须用一整张表格才能完全表述出来。表格中每个元素的start_pc、length及index项所 包含的信息,与LocalVariableTable属性中的这些项所包含的信息相同。
localvar_target 项可以说明局部变量的类型加了注解,但它并没有记录这个类型本身。我们可从适当的LocalVariableTable属性中查出该类型。
㊀ 这种情况的详细信息,可参阅《Type Annotations Specification (JSR 308) 》( http://types.cs.washington.edu/ jsr308/specification/java-annotation-design.html)的 3.3.7 小节。 ——译者注
catch_target {
u2 exception_table_index;
}
exception_table_index 项的值是对 exception_table 数组的索引,该数组位于 RuntimeVisibleTypeAnnotations 属性外围的 Code 属性中。
如果try语句带有multi-catch子句㊁,那么异常参数声明里面就会出现多个类型, 此时,异常参数的类型就是这些类型的并集(参见JLS § 14.20 )。对于并集中的每个类型来说,编译器通常会在exception_table中创建与之对应的元素,而我们可以通过catch_target项中的exception_table_index来区分这些元素。如此一来,就能保持类型与注解之间的对应关系了。
㊁ 可以同时捕获多种异常的 catch 子句,例40: catch (ClassNotFoundException | IllegalAccess-
Exception ex) { . . . } 。——译者注
offset_target {
u2 offset;
}
offset 项的值用来描述与 instanceof 表达式相对应的 instanceof 字节码指令在 code 数组里的偏移量、与new表达式相对应的new字节码指令在code数组里的偏移量,或是与方法引用表达式相对应的字节码指令在code数组里的偏移量。
type_argument_target {
u2 offset;
u1 type_argument_index;
}
offset 项的值是某条字节码指令相对于code数组的偏移量。该指令可能是与类型转换表达式相对应的指令、与new表达式相对应的new指令、与显式构造器调用语句相对应的指令、与方法调用表达式相对应的指令,或与方法引用表达式相对应的指令。 对于类型转换表达式来说,type_argument_index 项的值表示本条注解添加在类型转换操作符里的哪个类型上面。type_argument_index 的值如果是0,那就表示本注解添加在类型转换操作符的首个(或唯一的那个)类型上面。
在向交集类型(intersection type)转换的时候,转型表达式中会出现多个类型。
对于显式类型参数列表来说,type_argument_index 项的值表示本条注解添加在哪个类型参数上面。type_argument_index 的值如果是0,那就表示添加在首个类型参数上。
对于声明或表达式里面用到的某个类型来说,type_path 结构体可以指出该类型的哪一部分加了注解。注解可能会直接添加到该类型本身,但如果此类型是个引用类型的话,那么注解还可以添加在该类型的其他位置上:
例如,String[ ][ ] 这个类型的不同部分可以分别添加注解:
@Foo String[][] // Annotates the class type String
String @Foo[] [] // Annotates the array type String[][]
String [] @Foo [] // Annotates the array type String[]
Outer.Middle.Inner 这个嵌套类型的不同部分可以分别添加注解:
@Foo Outer.Middle.Inner
Outer.@Foo Middle.Inner
Outer.Middle.@Foo Inner
参数化类型 Map
@Foo Map<String, Object>
Map<@Foo String, Object>
Map<String, @Foo Object>
List<@Foo ? extends String>
List<? extends @Foo String>
type_path 结构的格式如下:
type_path {
u1 path_length;
{ u1 type_path_kind;
u1 type_argument_index;
} path[path_length];
}
path_length 项的值给出了path数组中的元素个数:
表4-15 各种type_path_kind取值的含义
值 | 含义 |
---|---|
0 | 注解位于数组类型的深处 |
1 | 注解位于嵌套类型的深处 |
2 | 注解添加在参数化类型中某个通配符类型参数的边界上 |
3 | 注解添加在参数化类型中的某个类型参数上 |
type_argument_index
type_path_kind 项的值如果是0、1或2,那么type_argument_index 项的值就是0。
type_path_kind 项的值如果是3,那么type_argument_index项的值会指出参数化类型中带有注解的那个类型参数,0表示本条注解添加在参数化类型中的首个类型参数上面。
表4-16 以@A Map<@B ? extends @C String, @D List<@E Object>> 中的各注解为例来演示type_path结构体
注解 | path_length | path |
---|---|---|
@A | 0 | [ ] |
@B | 1 | [(type_path_kind: 3; type_argument_index: 0}] |
@C | 2 | [(type_path_kind: 3; type_argument_index: 0 }, (type_path_ kind: 2; type_argument_index: 0}] |
@D | 1 | [(type_path_kind: 3; type_argument_index: 1)] |
@E | 2 | [{type_path_kind: 3; type_argument_index: 1), {type_path_ kind: 3; type_argument_index: 0}] |
表4-17 以@I String @F [ ] @G [ ] @H [ ] 中的各注解为例来演示type_path结构体
注解 | path_length | path |
---|---|---|
@F | 0 | [ ] |
@G | 1 | [(type_path_kind: 0; type_argument_index: 0)] |
@H | 2 | [(type_path_kind: 0; type_argument_index: 0 ), {type_path_ kind: 0; type_argument_index: 0)] |
@I | 3 | [{type_path_kind: 0; type_argument_index: 0 ), {type_path_ kind : 0 ; type_argument_index: 0 } , (type_path_kind: 0 ; type_argument_index: 0)] |
表4-18 以@A List<@B Comparable<@F Object @C [] @D [] @E []>>中的各注解为例来演示type_path结构体
注解 | path_length | path |
---|---|---|
@A | 0 | [ ] |
@B | 1 | [{type_path_kind: 3; type_argument_index: 0)] |
@C | 2 | [(type_path_kind: 3; type_argument_index: 0 }, (type_path_ kind: 3; type_argument_index: 0)] |
@D | 3 | [(type_path_kind: 3; type_argument_index: 0 }, (type_path_ kind : 3 ; type_argument_index: 0 ) , {type_path_kind: 0 ;type_argument_index: 0}] |
@E | 4 | [(type_path_kind: 3 ; type_argument_index : 0 } , {type_path_kind: 3; type_argument_index: 0 ),{type_path_kind:0 ; type_argument_index: 0 ) , {type_path_kind: 0 ; type_ argument_index: 0}] |
@F | 5 | [(type_path_kind: 3 ; type_argument_index: 0 ) , (type_path_kind: 3; type_argument_index: 0 ),(type_path_kind: 0 ; type_argument_index: 0 ) , {type_path_kind: 0; type_argument_index: 0 ), {type_path_kind: 0; type_argument_ index : 0}] |
表4-19 以@C Outer . @B Middle . @A Inner 中的各注解为例来演示type_path结构体
注解 | path_length | path |
---|---|---|
@A | 2 | [(type_path_kind: 1; type_argument_index: 0 }, (type_path_ kind: 1; type_argument_index: 0)] |
@B | 1 | [(type_path_kind: 1; type_argument_index: 0}] |
@C | 0 | [ ] |
表4-20 以Outer . Middle<@D Foo . @C Bar> . Inner<@B String @A [ ] > 中的各注解为例来演示type_path结构体
注解 | path_length | path |
---|---|---|
@A | 3 | [(type_path_kind: 1; type_argument_index: 0 ), {type_path_ kind: 1; type_argument_index: 0}, {type_path_kind: 3; type_ argument_index: 0}] |
@B | 4 | [(type_path_kind: 1; type_argument_index: 0 }, (type_path_ kind: 1; type_argument_index: 0}, {type_path_kind: 3; type_ argument_index: 0 ) , (type_path_kind: 0 ; type_argument_ index: 0}] |
@C | 3 | [(type_path_kind: 1; type_argument_index: 0 }, (type_path_ kind: 3; type_argument_index: 0), (type_path_kind: 1; type_ argument_index: 0)] |
@D | 2 | [(type_path_kind: 1; type_argument_index: 0 ), (type_path_ kind: 3; type_argument_index: 0}] |
RuntimelnvisibleTypeAnnotations 属性是ClassFile(见4.1节)、field_info(见4.5节)、method_info(见4.6节)结构或 Code 属性(见4.7.3小节)attributes 表中的变长属性。RuntimelnvisibleTypeAnnotations属性记录了标注在对应类声明、字段声明或方法声明所使用的类型上面的运行时不可见注解,也记录了标注在对应方法体中某个表达式所使用的类型上面的运行时不可见注解。此外,它还记录了标注在泛型类、 接口、方法及构造器的类型参数声明上面的运行时不可见注解。
在ClassFile结构、field_info结构、method_info结构或Code属性的属性表中,最多只能有1个 RuntimeInvisibleTypeAnnotations 属性。
只有当某个属性表的上级结构体或上级属性所对应的声明或表达式中带有加了注解的类型时,该属性表才可以包含RuntimeInvisibleTypeAnnotations属性。
RuntimeInvisibleTypeAnnotations 属性的格式如下:
RuntimelnvisibleTypeAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
type_annotation annotations[num_annotations];
}
RuntimelnvisibleTypeAnnotations_attribute 结构体的各项,其含义如下:
AnnotationDefault属性是个长度可变的属性,它出现在某些method_info结构体(见4.6节)的属性表里,而那种method_info结构体,则用来表示注解类型中的元素 (JLS § 9.6.1 )。 AnnotationDefault属性记录了由method_info结构所表示的那个元素的默认值(JLS § 9.6.2 )。Java虚拟机必须令默认值可供取用,以便使合适的反射API能够将其提供给调用者。
如果某个method_info结构体用来描述注解类型里的元素,那么该结构体的属性表中最多只能有1个AnnotationDefault属性。
AnnotationDefault属性的格式如下:
AnnotationDefault_attribute {
u2 attribute_name_index;
u4 attribute_length;
element_value default_value;
}
AnnotationDefault_attribute 结构各项的说明如下:
BootstrapMethods属性是变长属性,位于ClassFile(见4.1节)结构的属性表中。它用于保存由 invokedynamic 指令(见6.5节的invokedynamic小节)引用的引导方法限定符。
如果某个ClassFile结构的常量池表中有至少一个CONSTANT_InvokeDynamic_info(见4.4.10小节)成员,那么这个ClassFile结构的属性表就必须包含,且只能包含一个 BootstrapMethods 属性。
ClassFile结构的属性表中最多只能有一个BootstrapMethods属性。
BootstrapMethods属性的格式如下:
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref;
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];
} bootstrap_methods[num_bootstrap_methods];
}
BootstrapMethods_attribute 结构各项的说明如下:
bootstrap_methods [ ] 数组每个成员必须包含以下3项:
方法句柄的形式,由 invokedynamic(参见6.5节的invokedynamic小节)指令中调用点限定符(call site specifier)的持续解析过程来决定,java.lang.invoke.MethodHandle 类的 invoke 方法在执行的时候,要求引导方法的句柄必须能按传入的实际参数做出调整,就好似通过 java.lang.invoke.MethodHandle.asType 来调用一般。与之相应,CONSTANT_MethodHandle_info 结构 reference_kind 项的值应该是6或8(见5.4.3.5子小节),而 reference_index 项则应指明一个静态方法或构造器,它依次接受三个类型分别为 java.lang.invoke.MethodHandles.Lookup、String 及java.lang.invoke.MethodType 的参数。如果不符合上述要求,那么在调用点限定符的解析过程中,对引导方法句柄的调用就会失败。
Methodparameters属性是method_info结构(见4.6节)属性表中的变长属性。 MethodParameters属性记录了与形式参数有关的信息,例如参数名称等等。
method_info结构的属性表中,最多只能有1个MethodParameters属性。
MethodParameters属性的格式如下:
MethodParameters__attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 parameters_count;
{ u2 name_index;
u2 access_flags;
} parameters[parameters count];
}
MethodParameters_attribute 结构各项的含义如下:
parameters 数组中的每个元素都包含下列两项:
parameters 数组的第 i 项,对应于外围方法的描述符里的第 i 个参数描述符。(由于方法描述符最多只能有255个参数,因此parameters_count项只需占用1字节即可。) 实际上,这就意味着parameters数组保存了本方法所有参数的信息。也可以使用另一种存储方式,那就是令parameters数组里的项指出与之相对应的参数描述符,但那样做会使MethodParameters属性过于复杂。
parameters数组的第 i 项,可能与外围方法Signature属性(如果有的话)的第 i 个类型,或外围方法参数注解中的第 i 个注解相对应,也可能不与之对应。