字段表集合。

        字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。我们可以想一想在Java中描述一个字段可以包含什么信息?可以包括的信息有:字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。下表中列出了字段表的最终格式。

类型 名称 数量 类型 名称 数量
u2 access_flags 1 u2 attributes_count 1
u2 name_index 1 attribute_info attributes attributes_count
u2 descriptor_index 1      

        字段修饰符放在access_flags项目中,他与类中的access_flags项目是非常类似的,都是一个u2的数据类型,其中可以设置的标志位和含义见下表。

标志名称 标志值 含义
ACC_PUBLIC 0x0001 字段是否public
ACC_PRIVATE 0x0002 字段是否private
ACC_PROTECTED 0x0004 字段是否protected
ACC_STATIC 0x0008 字段是否static
ACC_FINAL 0x0010 字段是否final
ACC_VOLATILE 0x0040 字段是否volatile
ACC_TRANSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由编译器自动产生的
ACC_ENUM 0x4000 字段是否enum

        很明显,在实际情况中,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三个标志最多只能选择其一,ACC_FINAL、ACC_VOLATILE不能同时选择。接口之中的字段必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志,这些都是由Java本身的语言规则所决定的。

        根据access_flags标志的是两项索引值:name_index和descriptor_index。他们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。现在需要解释一下“简单名称”、“描述符”以及“全限定名”这三种特殊字符串的概念。

        全限定名和简单名称很好理解,以下面代码为例,“org/xxx/clazz/TestClass”是这个类的全限定名,仅仅是把类全名中的“.”替换成了“/”而已,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。简单名称是指没有类型和参数修饰的方法或者字段名称,这个类中的inc()方法和m字段的简单名称分别是“inc”和“m”。

public class TestClass {

    private int m;

    public int inc() {
        return m + 1;
    }
}

        相对于全限定名和简单名称来说,方法和字段的描述符就要复杂一些。描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表。

标识字符 含义 标识字符 含义
B 基本类型byte J 基本类型long
C 基本类型char S 基本类型short
D 基本类型double Z 基本类型boolean
F 基本类型float V 特殊类型void
I 基本类型int L 对象类型,如Ljava/lang/Object

        对于数组类型,每一维度将使用一个前置的“[”字符来描述,如一个定义为“java.lang.String[][]”类型的二维数组,将被记录为:“[[Ljava/lang/String;”,,一个整型数组“int[]”被记录为“[I”。

        用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“( )”之内。如方法void inc()的描述符为“( ) V”,方法java.lang.String toString()的描述符为“( ) LJava/lang/String;”,方法int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为“([CII[CIII) I”。

        对于上面代码的TestClass.class文件来说,字段表集合从地址0x000000F*开始,第一个u2类型的数据为容量计数器fields_count,如下图所示,其值为0x0001,说明这个类只有一个字段表数据。接下来紧跟着容量计数器的是access_flags标志,值为0x0002,代表private修饰符的ACC_PRIVATE标志位为真(ACC_PRIVATE标志的值为0x0002),其他修饰符为假。代表字段名称的name_index的值为0x0005,从常量表中可查的第5项常量是一个CONSTANT_UTF8_info类型的字符串,其值为“m”,代表字符描述符的descriptor_index的值为0x0006,指向常量池的字符串“I”,根据这些信息,我们可以推断出原代码定义的字段为:“private int m;”。

字段表集合。_第1张图片

        字段表都包含的固定数据项目到descriptor_index为止就结束了,不过在descriptor_index之后跟随着一个属性表集合用于存储一些额外的信息,字段都可以在属性表中描述零至多项的额外信息。对于本例中的字段m,他的属性表计数器为0,也就是说没有需要额外描述的信息,但是,如果将字段m的声明改为“final static int m=123”,那就可能会存在一项名称为ConstantValue的属性,其值指向常量123。

        字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。

你可能感兴趣的:(JVM)