# Class 文件解析 (下)

# 类索引,父类索引,接口索引集合

  • 在访问标记后,会指定该类的类别,父类类别以及实现的接口,格式如下 :

    长度含义
    u2this_class
    u2super_class
    u2interfaces_count
    u2interfaces[interfaces_count]
  • 这三项数据来确定这个类的继承关系

    • 类索引用于确定这个类的全限定名
    • 父类索引用于确定这个类的父类的全限定名。由于 Java 语言不允许多重继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 Java 类都有父类。因此除了 java.lang.Object 外,所有的 Java 类的父类索引都不为 0
    • 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句 (如果这个类本身是一个接口,则应当是 extends 语句) 后的接口顺序从左到右排列在接口索引集合中
  1. this_class (类索引)

    • 字节无符号整数,指向常量池的索引。它提供了类的全限定名,如 com/wong/Demo. this_class 的值必须是对常量池表中某项的一个有效索引值。常量池在这个索引处的成员必须为 CONSTANT_Class_info 类型结构体,该结构体表示这个 class 文件所定义的类或接口
  2. super_class (父类索引)

    • 2 字节无符号整数,指向常量池的索引。它提供了当前类的父类的全限定名。如果没有继承任何类,其默认继承的是 java/lang/Object 类。同时,由于 Java 不支持多继承,所以其父类只有一个
    • superclass 指向的父类不能是 final
  3. interfaces

    • 指向常量池索引集合,它提供了一个符号引用到所有已实现的接口
    • 由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的 CONSTANT_Class (这里必须是接口而不是类)

3.1. interfaces_count (接口计数器)

​ interfaces_count 项的值表示当前类或接口的直接超接口数量

3.2. interfaces [] (接口索引集合)

interfaces [] 中每个成员的值必须是对常量池表中某项的有效索引值,它的长度位 interfaces_count. 每个成员 interfaces [i] 必须为 CONSTANT_Class_info 结构,其中 0 <= i< interfaces_count. 在 interfaces [] 中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序 (从左至右) 一样,即 interfaces [0] 对应的是源代码中最左边的接口

# 字段表集合

  • 用于描述接口或类中声明的变量。字段 (field) 包括类级变量以及实例级变量,但是不包括方法内部,代码块内部声明的局部变量
  • 字段叫什么名字,字段被定义为什么数据类型,这些都是无法固定的,智能引用常量池中的常量来描述
  • 它指向常量池索引集合,它描述了每个字段的完整信息。比如 == 字段的标识符,访问修饰符 (public, private 或 protected), 是类变量还是实例变量 (static 修饰符), 是否常量 (final 修饰符)== 等
  • 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本 Java 代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问行,会自动添加指向外部类实例的字段
  • 在 Java 语言中字段是无法重载的,两个字段的数据类型,修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的
  1. fields_count (字段计数器)

    fields_count 的值表示当前 class 文件 fields 表的成员个数。使用两个字节来表示

    fields 表中每个成员都是一个 field_info 结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段

  2. fields [] (字段表)

    • fields 表中的每个成员都必须是一个 fields_info 结构的数据项,用于表示当前类或接口中某个字段的完整描述

    • 一个字段的信息包括如下信息,这些信息中,各个修饰符都是布尔值,要么有,要么没有

      • 作用域 (public, private, protected 修饰符)
      • 是实例变量还是类变量 (static 修饰符)
      • 可变性 (final)
      • 并发可见性 (volatile 修饰符,是否强制从主内存读写)
      • 可否序列化 (transient 修饰符)
      • 字段数据类型 (基本数据类型,对象,数组)
      • 字段名称
    • 字段表结构

      类型名称含义数量
      u2access_flags访问标志1
      u2name_index字段名索引1
      u2descriptor_index描述符索引1
      u2attributes_count属性计数器1
      attribute_infoattributes属性集合attributes_count
  3. 字段表访问标识

    标志名称标志值含义
    ACC_PUBLIC0x0001字段是否为 public
    ACC_PRIVATE0x0002字段是否为 private
    ACC_PROTECTED0x0004字段是否为 protected
    ACC_STATIC0x0008字段是否为 static
    ACC_FINAL0x0010字段是否为 final
    ACC_VOLATILE0x0040字段是否为 volatile
    ACC_TRANSTENT0x0080字段是否为 transient
    ACC_SYNCHETIC0x1000字段是否为编译器自动产生
    ACC_ENUM0x4000字段是否为 enum
  4. 字段名索引

    根据字段名索引的值,查询常量池中的指定索引项即可

  5. 描述符索引

    描述符是用来描述字段的数据类型,方法的参数列表 (包括数量,类型以及顺序) 和返回值。根据描述符规则,基本数据类型 (byte, char, double, float, int, long, short, boolean) 及代表无返回值的 void 类型都用一个大写字符来表示,二对象则用字符 L 加对象的全限定名来表示

    1. 属性表集合

      一个字段还可能拥有一些属性,用于储存更多的额外信息。比如初始化值,一些注释信息等。属性个数存放在 attribute_count 中,属性具体内容放在 attributes 数组中.

      以常量属性为例,结构为 :

      ConstantValue_attribute {

      ​ u2 attribute_name_index;

      ​ u4 attribute_length;

      ​ u2 constantvalue_index;

      }

      对于常量属性而言,attribute_length 值恒为 2

# 方法表集合

  • 在字节码文件中,每一个 method_info 项都对应着一个类或者接口中的方法信息. 比如方法的访问修饰符 (public, private 或 protected), 方法的返回值类型以及方法的参数信息等

  • 如果这个方法不是抽象的或者不是 native 的,那么字节码中会体现出来

  • methods 表中只是描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法. method's 表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息 (比如:类 (接口) 初始化方法 <client>() 和实例初始化方法 <init>() )

  • 注意事项

    在 Java 中,要重载 (Overload) 一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此 Java 里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在 Class 文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个 class 文件中

    也就是说,尽管 Java 语法规范并不允许在一个类或接口中声明多个方法签名相同的方法,但是和 Java 羽凡规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同.

  1. methods_count (方法计数器)

    methods_count 的值标识当前 class 文件 methods 表的成员规格书。使用两个字节标识

    methods 表中每个成员都是一个 method_info 结构

  2. methods [] (方法表)

    • methods 表中的每个成员都必须是一个 method_info 结构,用于标识当前类或接口中某个方法的完整描述。如果某个 method_info 结构的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么该结构中也应包含实现这个方法所用的 Java 虚拟机指令

    • method_info 结构可以标识类和接口中定义的所有方法,包含实例方法,类方法,实例初始化方法和类或接口初始化方法

      类型名称含义数量
      u2access_flags访问标志1
      u2name_index方法名索引1
      u2descriptor_index描述符索引1
      u2attributes_count属性计数器1
      attribute_infoattributes属性集合attributes_count

# 属性表集合

方法表集合之后的属性表集合,指的是 class 文件所携带的辅助信息,比如该 class 文件的源文件的名称。以及任何带有 RetentionPolicy.CLASS 或者 RetentionPolicy.RUNTIME 的注解。这类信息通常被用于 Java 虚拟机的验证和运行,以及 Java 程序的调试,一般无需深入了解

此外,字段表,方法表都可以有自己的属性表。用于描述某些场景专有的信息

属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但 Java 虚拟机运行时会忽略掉它不认识的属性