初探Java字节码格式,编程解析字节码文件

参考资料:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

什么是Java字节码文件?

在刚开始学习Java时,很多教程会教学生们,使用记事本编写一个HelloWorld.java的.java文件。

public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello, World");
	}
}

然后在命令行使用javac进行编译,得到HelloWorld.class文件,这个已.class为后缀的文件就是字节码(bytecode)文件。第一节计算机程序课时,老师就会告诉我们计算机只认识0和1。同样Java虚拟机也只认识有着严格格式规定的以8位字节为基础单位的二进制流——字节码文件。除了Java,现在已经有很多高级编程语言(如:Groovy,Scala等)都可以被编译为字节码文件,在Java虚拟机上运行。
下图是HelloWorld.class文件内容:
HelloWorld.class

Java字节码文件的格式

.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
下面是官方文档中给出的.class文件的结构,下面我将对照这个格式表,解析HelloWorld.class中的字节序列。图中的u表示无符号字节,例如u1表示一个无符号字节、u4表示四个无符号字节。
在这里插入图片描述

magic

魔数,我们一般会使用文件后缀来表示文件的类型,但是文件后缀容易被修改,很多类型的文件其起始几个字节的内容是固定的,这样就算文件的后缀被修改过,计算机还是知道文件原来的类型。class文件的魔数为开始4个字节,为十六进制的CAFEBABE.(cafe babe~)
在这里插入图片描述

minor_version 与 major_version

紧接着的四个字节被用来说明字节码的版本,前两个字节代表次要版本号(minor_version),后两个字节表示主要版本号(major_version)。主要版本号与次要版本号共同决定字节码文件的格式。同时,Java虚拟机在运行字节码文件前会根据字节码文件的版本号判断是否可以运行,例如,JDK 1.1能支持版本号为45.0~45.65535的Class文件,无法执行版本号为46.0以上的Class文件,而JDK 1.2则能支持45.0~46.65535的Class文件。
图中的 minor_version0x0000major_version0x0034,合起来版本号就是:52.0;
在这里插入图片描述

constant_pool_count

紧接着版本号后面的是常量池的入口。常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。
因为不同字节码文件的常量池中的常量数量是不一样的,所以不同的字节码文件这一部分的所占的字节数也是不一样的(其实即使常量数量一样,由于常量类别不一样也会导致常量池的字节数不一样,具体看下面的constant_pool部分)。
为了让虚拟机准确的解析常量池中的常量,字节码结构中是这样设计的,先使用两个字节,指明常量的数量,然后在之后的字节中依次记录每个常量的信息,这两个字节就是constant_pool_count
如下图中,constant_pool_count 对应的两个字节为 0x001D, 也就是十进制的 29.
在这里插入图片描述
实际上常量池中实际的常量数量应该是28,甚至更少。设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示。(如java.lang.Object中字节码文件中的super_class向为0x0000代表它没有父类,其它字节码文件的super_class值均不为0).
此外对于DoubleLong类型这种占用8字节的常量需要占用2个索引长度,为什么这么设计?官网是这么说的(x_x#):

In retrospect, making 8-byte constants take two constant pool entries was a poor choice.
回想起来,让8字节常量带有两个常量池条目是一个糟糕的选择。

constant_pool

紧接着constant_pool_count后面的就是常量池部分了。常量池中存储了各种类型的常量,包括字符串常量、类和接口的名字,属性域名等。不同类型的常量有不同的数据格式,但它们都有以下常规格式:
在这里插入图片描述
也就是说,一个常量的第一个字节为tag,该字节代表该常量的类型,以下是tag的值与常量类型的对照表:
在这里插入图片描述
每一种常量类型又对应着一种结构。如CONSTANT_Class类型的常量,格式如下:
在这里插入图片描述
CONSTANT_Class类型的常量由3个字节构成,第一个字节是上面提到的tag,而且值为7。接下来两个字节为name_index 它是由两个字节组成的一个索引,指向常量池中指定位置的UTF8常量。
由于篇幅关系,这里不一一列举所有常量类型的数据结构,有需要可以到官方文档研究: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140

图中选中的字节为该字节码文件种的常量池部分(注意:这里知道常量池的结束位置是因为笔者已提前将所有常量都解析完了)。
在这里插入图片描述
下面我们对照着HelloWorld.class字节码文件具体看几个例子:
在这里插入图片描述

  1. 常量池中的第一个常量
    常量池中的第一个常量,读取常量池的第一个字节,发现是0x0A,也就是十进制的 10,对照上面的常量类型与tag的对照表,可以发现10对应的常量类型为CONSTANT_Methodref_info,于是在官网找到该类型的结构如下:
    在这里插入图片描述
    根据CONSTANT_Methodref_info类型的结构可知,该常量一共占5个字节,第一个字节为 tag,接着的两个字节为class_index,最后两个字节为name_ane_type_index.
    在这里插入图片描述
    所以我们就解析出了第一个常量,它包含了两个索引,classIndex指向常量池中的第6个常量,nameAndTypeIndex指向常量池中第15个常量(括号中的值为字节数组的十进制显示,#num代表常量池中的第num个常量,本文中其他部分出现此种写法也是相同意义,不再作说明):
#  1 MethodRef	classIndex=[0, 6](#6), nameAndTypeIndex=[0, f](#15)
  1. 常量池中的第二个常量
    在这里插入图片描述
    紧接着第一个常量的是第二个常量,读取第一个字节,发现是0x09,也就是十进制的 9,对照上面的常量类型与tag的对照表,可以发现9对应的常量类型为CONSTANT_Fieldref_info,于是在官网找到该类型的结构如下:
    在这里插入图片描述
    根据CONSTANT_Fieldref_info类型的结构可知,该常量一共占5个字节,第一个字节为 tag,接着的两个字节为class_index,最后两个字节为name_ane_type_index.

在这里插入图片描述
所以我们就解析出了第二个常量,它包含了两个索引,classIndex指向常量池中的第16个常量,nameAndTypeIndex指向常量池中第17个常量:

	#  2 FieldRef	classIndex=[0, 10](#16), nameAndTypeIndex=[0, 11](#17)	
  1. UTF8类型的常量
    通过上面的两个例子,相信聪明的你已经摸清了一些门路,大多数的常量类型都会通过索引来指向其他类型的常量,比如上面的CONSTANT_Methodref常量中就包含了一个CONSTANT_Class类型常量的索引和一个CONSTANT_NameAndType类型常量的索引。
    当然也有一些类型的常量,不包含指向其他常量的索引,而是存储自身的数据。例如数字类型的常量(CONSTANT_IntegerCONSTANT_FloatCONSTANT_LongCONSTANT_Double)和UTF8常量(CONSTANT_Utf8)。

CONSTANT_IntegerCONSTANT_Float常量结构:
在这里插入图片描述
CONSTANT_Long_infoCONSTANT_Double_info常量结构:
在这里插入图片描述

下面我们主要看一看比较有意思的UTF8类型的常量,下面是CONTANT_Utf8的结构:

在这里插入图片描述

从上面的结构我们可以看出,Utf8常量类型由三部分构成:
a. 第一部分为tag,对于Utf8类型常量,它的值为0x01
b. 接下来两个字节为length,表示第三部分bytes的长度。
c. length之后的就是bytes部分,该部分表示一个字符串。

下面是HelloWorld.class文件中第一个Utf8常量(该常量为本字节码文件中的第7个常量,中间还有几个常量(#3,#4,#5,#6)的解析过程没有列举,读者感兴趣可以自己手动解析一下,它们所占字节的位置也一并标在了下图中)。
在这里插入图片描述
所以我们就解析出了该UTF8常量,它代表的字符串长度为0x0006也就是十进制的6。字符串的内容为[3c,69,6e,69,74,3e],代表的值为:<init>。

	#  7 Utf8	length=[0, 6](6), data=[3c, 69, 6e, 69, 74, 3e]

至此,读者应该完全掌握了字节码文件常量池中常量的表示形式。
HelloWorld.class的常量池中的常量全部解析后,得到(对于非UTF8常量,后面的字符串为根据索引计算出的描述):

	#  1 MethodRef	classIndex=[0, 6](#6), nameAndTypeIndex=[0, f](#15)	// java/lang/Object.<init>:()V
	#  2 FieldRef	classIndex=[0, 10](#16), nameAndTypeIndex=[0, 11](#17)	// java/lang/System.out:Ljava/io/PrintStream;
	#  3 String	stringIndex=[0, 12](#18)	// Hello, World
	#  4 MethodRef	classIndex=[0, 13](#19), nameAndTypeIndex=[0, 14](#20)	// java/io/PrintStream.println:(Ljava/lang/String;)V
	#  5 Class	nameIndex=[0, 15](#21)	// HelloWorld
	#  6 Class	nameIndex=[0, 16](#22)	// java/lang/Object
	#  7 Utf8	length=[0, 6](6), data=[3c, 69, 6e, 69, 74, 3e]	// <init>
	#  8 Utf8	length=[0, 3](3), data=[28, 29, 56]	// ()V
	#  9 Utf8	length=[0, 4](4), data=[43, 6f, 64, 65]	// Code
	# 10 Utf8	length=[0, f](15), data=[4c, 69, 6e, 65, 4e, 75, 6d, 62, 65, 72, 54, 61, 62, 6c, 65]	// LineNumberTable
	# 11 Utf8	length=[0, 4](4), data=[6d, 61, 69, 6e]	// main
	# 12 Utf8	length=[0, 16](22), data=[28, 5b, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b, 29, 56]	// ([Ljava/lang/String;)V
	# 13 Utf8	length=[0, a](10), data=[53, 6f, 75, 72, 63, 65, 46, 69, 6c, 65]	// SourceFile
	# 14 Utf8	length=[0, f](15), data=[48, 65, 6c, 6c, 6f, 57, 6f, 72, 6c, 64, 2e, 6a, 61, 76, 61]	// HelloWorld.java
	# 15 NameAndType	nameIndex=[0, 7](#7), descriptorIndex=[0, 8](#8)	// <init>:()V
	# 16 Class	nameIndex=[0, 17](#23)	// java/lang/System
	# 17 NameAndType	nameIndex=[0, 18](#24), descriptorIndex=[0, 19](#25)	// out:Ljava/io/PrintStream;
	# 18 Utf8	length=[0, c](12), data=[48, 65, 6c, 6c, 6f, 2c, 20, 57, 6f, 72, 6c, 64]	// Hello, World
	# 19 Class	nameIndex=[0, 1a](#26)	// java/io/PrintStream
	# 20 NameAndType	nameIndex=[0, 1b](#27), descriptorIndex=[0, 1c](#28)	// println:(Ljava/lang/String;)V
	# 21 Utf8	length=[0, a](10), data=[48, 65, 6c, 6c, 6f, 57, 6f, 72, 6c, 64]	// HelloWorld
	# 22 Utf8	length=[0, 10](16), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 4f, 62, 6a, 65, 63, 74]	// java/lang/Object
	# 23 Utf8	length=[0, 10](16), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 79, 73, 74, 65, 6d]	// java/lang/System
	# 24 Utf8	length=[0, 3](3), data=[6f, 75, 74]	// out
	# 25 Utf8	length=[0, 15](21), data=[4c, 6a, 61, 76, 61, 2f, 69, 6f, 2f, 50, 72, 69, 6e, 74, 53, 74, 72, 65, 61, 6d, 3b]	// Ljava/io/PrintStream;
	# 26 Utf8	length=[0, 13](19), data=[6a, 61, 76, 61, 2f, 69, 6f, 2f, 50, 72, 69, 6e, 74, 53, 74, 72, 65, 61, 6d]	// java/io/PrintStream
	# 27 Utf8	length=[0, 7](7), data=[70, 72, 69, 6e, 74, 6c, 6e]	// println
	# 28 Utf8	length=[0, 15](21), data=[28, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b, 29, 56]	// (Ljava/lang/String;)V
access_flags

常量池之后的两个字节就access_flags,类访问标志。
在这里插入图片描述
在我们的HelloWorld.classaccess_flags对应的字节为0x0021,对照上面的类访问标志表可知,HelloWorld类的访问标识为 ACC_PUBLIC,ACC_FINAL
在这里插入图片描述

this_class

access_flags之后的两个字节,这两个字节的值代表常量池中的一个索引,且常量池中该索引处的常量为一个CONSTANT_Class_info的常量,该常量代表此字节码文件所定义的类或接口。
在这里插入图片描述
HelloWorld.classthis_class对应的字节为0x0005,就是十进制的5,对应了常量池中的索引为5的常量,也就是

#  5 Class	nameIndex=[0, 15](#21)	// HelloWorld
super_class

this_class之后的两个字节,它代表本字节码文件所定义的类的父类。
如果这两个字节为0x0000,这个字节码文件一定是java.lang.Object类的字节码文件,唯一一个没有父类的类。其他情况下,这两个字节的值代表常量池中的一个索引,且常量池中该索引处的常量为一个CONSTANT_Class_info的常量。
在这里插入图片描述
HelloWorld.class中,super_class对应的两个字节为0x0006,对应了常量池中索引为6的常量,也就是

	#  6 Class	nameIndex=[0, 16](#22)	// java/lang/Object
interfaces_count与Interfaces[]

super_class之后的两个字节代表此字节码文件代表的类或接口所直接实现的父接口数量。
HelloWorld.class中,interfaces_count对应的两个字节为0x0000,就表示HelloWorld类没有实现任何接口。
在这里插入图片描述
紧接着在interfaces_count的就是各个接口的信息,每个接口信息由两个字节表示,它表示一个常量池的索引,常量池该索引处的常量必定是一个CONSTANT_class类型的常量。这一部分的字节数为接口数量的两倍。

由于HelloWorld.class中的接口数量为0,所以interfaces[]内容字节数也为0.

fields_count与fields[]

紧接着interfaces_count后的两个字节是fields_count,它表示一个类或接口中的字段数量,包括类变量和实例变量。
HelloWorld.class文件中,fields_count对应的两个字节为0x0000,表示HelloWorld类中字段数为0.
在这里插入图片描述
紧接着fields_count后面的字符为各个字段的信息,字段的结构如下图所示:
在这里插入图片描述
由于HelloWorld.class文件中的fields_count的值为0,所以fields[]内容字节数也为0.

methods_count与methods[]

紧接着fields[]后面的两个字节表示类中方法的数量methods_count.
HelloWorld.class文件中methods_count对应的两个字节为0x0002,表示HelloWorld类中有两个方法。
在这里插入图片描述
紧接着methods_count之后的字节表示各个方法mehods[],下面是方法信息的结构:
在这里插入图片描述
表示方法信息的头两个字节是方法的访问标记access_flags,对照下表可以解析出这两个字节代表的含义。
在这里插入图片描述
接下来的name_indexdescriptor_index又是常量池的索引。
接下来的attributes_countattributes里面存储了该方法的虚拟机指令等信息,由于该部分的内容较多,本文章不详细介绍,有兴趣可以查阅官方文档或相关书籍。

HelloWorld.class文件中,表示第一个方法的字节为下图中选中的部分。
在这里插入图片描述
access_flags:0x0001,表示:ACC_PUBLIC,
name_index:0x0007,表示:常量池的索引为7处的常量,该常量的值为<init>,说明该方法时构造方法。

	#  7 Utf8	length=[0, 6](6), data=[3c, 69, 6e, 69, 74, 3e]	// <init>

descriptor_index:0x0008,表示:常量池的索引为7处的常量,该常量的值为()V,括号中的内容为该方法的参数列表此处为空,后面的V代表此方法的返回值为void,无返回值。

	#  8 Utf8	length=[0, 3](3), data=[28, 29, 56]	// ()V

attributes_count:0x0001,表示后面有一个attribute
attribute_name_index:0x0009,表示:常量池的索引为9处的常量,该常量的值为Code.说明这个属性描述的是该方法JVM的指令和辅助信息。
attribute_length:0x0000001D,值为29,表示接下来的29个字节就是这个属性的info.
attribute_info:[0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2a, 0xb7, 0x0, 0x1, 0xb1, 0x00, 0x00, 0, 0x01, 0x00,0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00x01],有兴趣的读者可以尝试按照Code属性的结构解析这部分信息。
在这里插入图片描述

attributes_count 与 attributes[]

类似于有多种常量类型,属性的类型也是多样的,下面是所有属性都遵循的常规格式:
在这里插入图片描述
首先两个字节表示属性名字的在常量池中的索引,接下来的四个字节表示info部分的字节数,最后的info部分为属性信息,对于不同的属性类别,对该部分的解析方法不同。下表是预定义的类文件属性。
在这里插入图片描述
由于属性的种类较多,本文不一一讨论各个类型的属性的解析过程。

HelloWorld.classattributes_count为0x0001,说明attributes[]的长度为1;
在这里插入图片描述

attribute_name_index:0x000d,代表常量池的索引为13处的常量,值为:SourceFile。
在这里插入图片描述
查询SourceFile的结构为:
在这里插入图片描述
attribute_name_index:0x000d,代表常量池的索引为13处的常量,值为:SourceFile。
attribute_length:0x00000002,值为2,此属性的info为接下来的两个字节。
sourcefile_index(info):0x000e,代表常量池的索引为14处的常量,值为:HelloWorld.java.

字节码格式实战——编程实现解析字节码文件中的常量池。

由于上文已经详细介绍了字节码文件的格式,且程序代码较多,此处不会贴出全部代码,有需要可以前往下载:下载地址

设计ClassFile类

根据上文中提到的字节码格式,设计ClassFile类。该类包含的属性如下图,图中ByteData类型可以理解为字符数组(byte[])。
在这里插入图片描述

对于基本数组类型的解析

从字节流中读取指定长度的字节填充ClassFile实例对象的字段。下面是魔数,版本号,常量池大小等字段的解析过程。

    File file = new File(classFilePath);
    FileReader fileReader = new FileReader(file);
    byte[] bytes = fileReader.readBytes();

    //字节流对象
    ByteStream byteStream = new ByteStream(bytes);

    ClassFile classFile = new ClassFile();

    classFile.setMagic(byteStream.readByteData(4));

    classFile.setMinorVersion(byteStream.readByteData(2));

    classFile.setMajorVersion(byteStream.readByteData(2));

    ByteData constantPoolCount = byteStream.readByteData(2);
    classFile.setConstantPoolCount(constantPoolCount);
对于常量池等复杂类型的解析

这部分主要由2个关键类及各种常量类构成:

  1. ConstantTypeRegister(常量类型注册表)
    这个类中注册了所有常量类的类型,通过tag就能找到指定常量的类型。
public class ConstantTypeRegister {
   private Map<Byte,Class<? extends Constant>>constantTypeMap;
   private static ConstantTypeRegister constantTypeRegister = new ConstantTypeRegister();

   private ConstantTypeRegister(){
       constantTypeMap = new HashMap<>();
       register((byte) 7,ClassInfo.class);
       register((byte) 9,FieldRefInfo.class);
       register((byte) 10,MethodRefInfo.class);
       register((byte) 11,InterfaceMethodRefInfo.class);
       register((byte) 8,StringInfo.class);
       register((byte) 3,IntegerInfo.class);
       register((byte) 4,FloatInfo.class);
       register((byte) 5,LongInfo.class);
       register((byte) 6,DoubleInfo.class);
       register((byte) 12,NameAndTypeInfo.class);
       register((byte) 1,Utf8Info.class);
       register((byte) 15, MethodTypeInfo.class);
       register((byte) 16,MethodTypeInfo.class);
       register((byte) 18,InvokeDynamicInfo.class);
   }

   public  void register(byte b,Class<? extends Constant> t){
       constantTypeMap.put(b,t);
   }

   public Class<? extends Constant> getConstantType(byte b){
       return constantTypeMap.get(b);
   }

   public static ConstantTypeRegister getInstance() {
       return constantTypeRegister;
   }

}
  1. ConstantPool(常量池)
    可以根据字节流和常量个数解析出各个类型的常量并保存在一个数组中,此类的内部类还提供描述解析功能。
public class ConstantPool extends Item{

    /**
     * 常量类型注册表
     */
    private ConstantTypeRegister constantTypeRegister = ConstantTypeRegister.getInstance();

    /**
     * 描述解析器 将 #{num} 字符串解析为num位置常量所代表的意思
     */
    private DescriptionParser descriptionParser = new DescriptionParser();

    /**
     * 常量数组
     */
    private Constant[] constants;

    /**
     * 使用字节流和常量个数的构造函数,该构造函数中会从字节流读取需要的字节完成常量池的构造
     * @param byteStream
     * @param count
     */
    public ConstantPool(ByteStream byteStream, int count){
        constants = new Constant[count];
        for(int i=1;i<count;i++){
            constants[i] = nextConstant(byteStream);
            // Long与Double类型占两个常量长度
            if(constants[i] instanceof LongInfo||constants[i] instanceof DoubleInfo){
                i++;
            }
        }

    }

    // 构造下一个常量
    private Constant nextConstant(ByteStream byteStream){
        // 读取 tag
        Tag tag = new Tag(byteStream);
        // 从注册表中取出tag对应的常量类型并实例化常量
        Class<? extends Constant> constantType = constantTypeRegister.getConstantType(tag.getTagNum());
        try {
            Constant constant = constantType.getConstructor(ByteStream.class).newInstance(byteStream);
            return constant;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Constant[] getConstants() {
        return constants;
    }

    public void setConstants(Constant[] constants) {
        this.constants = constants;
    }


    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("Constants pool:");
        for(int i=0;i<constants.length;i++){
            Constant c = constants[i];
            if(c==null)
                sb.append("\n");
            else
                sb.append(String.format("\t#%3d %s\t// %s\n",i,constants[i],
                        descriptionParser.parse(constants[i].description())));
        }

        return sb.toString();
    }

    /**
     * 返回常量池中指定索引的常量的字符串描述
     * @param index
     * @return
     */
    public String parse(int index){
        return descriptionParser.parse(constants[index].description());
    }

    public String parse(String originStr){
        return descriptionParser.parse(originStr);
    }


    public String description() {
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<constants.length;i++){
            Constant c = constants[i];
            if(c==null)
                sb.append("\n");
            else
                sb.append(String.format("\t#%3d %s\t// %s\n",i,constants[i],
                        descriptionParser.parse(constants[i].description())));
        }
        return sb.toString();
    }

    public class DescriptionParser{
        private Map<String,String> descriptionMap;

        public DescriptionParser(){
            descriptionMap = new HashMap<>();
        }


        public String parse(String description){
            String s = descriptionMap.get(description);
            if(s !=null){
                return s;
            }
            String originDescription = description;
            Pattern pattern = Pattern.compile("#\\{[0-9]+\\}");
            Matcher matcher = pattern.matcher(description);

            while(matcher.find(0)){
                String subString = matcher.group();
                int constantIndex = Integer.parseInt(subString.substring(2, subString.length() - 1));
                description= matcher.replaceFirst(Matcher.quoteReplacement(parse(constants[constantIndex].description())));
                matcher = pattern.matcher(description);
            }

            descriptionMap.put(originDescription,description);
            return description;
        }
    }
}
  1. Constants及其子类们
    具体的常量类型,它们都提供一个参数为ByteStream类型的构造函数以便利用反射构造,简化代码。下面是表示CONSTANT_Class常量类型的ClassInfo类:
public class ClassInfo extends Constant {
    private ByteData nameIndex;     // u2
    public ClassInfo(ByteStream byteStream){
        setTag(new Tag((byte)7));
        nameIndex = byteStream.readByteData(2);
    }
    @Override
    public String toString() {
        return "Class\tnameIndex=" + nameIndex+"(#"+Utils.bytesToInt(nameIndex.getBytes())+")";
    }
    @Override
    public String getTypeDesc() {
        return "Class";
    }
    @Override
    public String description() {
        int index = Utils.bytesToInt(nameIndex.getBytes());
        return "#{"+ index +"}";
    }
}
总结

常量池部分解析完之后的类,父类,接口数量,接口,字段数量,字段等解析都比较简单,只是最后属性的部分,由于属性表结构属性类型比较复杂,使用这种方法解析工作量貌似比较大,所以笔者也没有最终完成对该部分的编程实现。

附录

HelloWorld.java

public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello, World");
	}
}

HelloWorld.class文件解析结果:

=================================================================================
魔数: [ca, fe, ba, be]
次版本: 0
主版本: 52: HelloWorld
父类: java/lang/Object
接口:

类访问标志: ACC_PUBLIC,ACC_SUPER
常量池:

	#  1 MethodRef	classIndex=[0, 6](#6), nameAndTypeIndex=[0, f](#15)	// java/lang/Object.<init>:()V
	#  2 FieldRef	classIndex=[0, 10](#16), nameAndTypeIndex=[0, 11](#17)	// java/lang/System.out:Ljava/io/PrintStream;
	#  3 String	stringIndex=[0, 12](#18)	// Hello, World
	#  4 MethodRef	classIndex=[0, 13](#19), nameAndTypeIndex=[0, 14](#20)	// java/io/PrintStream.println:(Ljava/lang/String;)V
	#  5 Class	nameIndex=[0, 15](#21)	// HelloWorld
	#  6 Class	nameIndex=[0, 16](#22)	// java/lang/Object
	#  7 Utf8	length=[0, 6](6), data=[3c, 69, 6e, 69, 74, 3e]	// <init>
	#  8 Utf8	length=[0, 3](3), data=[28, 29, 56]	// ()V
	#  9 Utf8	length=[0, 4](4), data=[43, 6f, 64, 65]	// Code
	# 10 Utf8	length=[0, f](15), data=[4c, 69, 6e, 65, 4e, 75, 6d, 62, 65, 72, 54, 61, 62, 6c, 65]	// LineNumberTable
	# 11 Utf8	length=[0, 4](4), data=[6d, 61, 69, 6e]	// main
	# 12 Utf8	length=[0, 16](22), data=[28, 5b, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b, 29, 56]	// ([Ljava/lang/String;)V
	# 13 Utf8	length=[0, a](10), data=[53, 6f, 75, 72, 63, 65, 46, 69, 6c, 65]	// SourceFile
	# 14 Utf8	length=[0, f](15), data=[48, 65, 6c, 6c, 6f, 57, 6f, 72, 6c, 64, 2e, 6a, 61, 76, 61]	// HelloWorld.java
	# 15 NameAndType	nameIndex=[0, 7](#7), descriptorIndex=[0, 8](#8)	// <init>:()V
	# 16 Class	nameIndex=[0, 17](#23)	// java/lang/System
	# 17 NameAndType	nameIndex=[0, 18](#24), descriptorIndex=[0, 19](#25)	// out:Ljava/io/PrintStream;
	# 18 Utf8	length=[0, c](12), data=[48, 65, 6c, 6c, 6f, 2c, 20, 57, 6f, 72, 6c, 64]	// Hello, World
	# 19 Class	nameIndex=[0, 1a](#26)	// java/io/PrintStream
	# 20 NameAndType	nameIndex=[0, 1b](#27), descriptorIndex=[0, 1c](#28)	// println:(Ljava/lang/String;)V
	# 21 Utf8	length=[0, a](10), data=[48, 65, 6c, 6c, 6f, 57, 6f, 72, 6c, 64]	// HelloWorld
	# 22 Utf8	length=[0, 10](16), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 4f, 62, 6a, 65, 63, 74]	// java/lang/Object
	# 23 Utf8	length=[0, 10](16), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 79, 73, 74, 65, 6d]	// java/lang/System
	# 24 Utf8	length=[0, 3](3), data=[6f, 75, 74]	// out
	# 25 Utf8	length=[0, 15](21), data=[4c, 6a, 61, 76, 61, 2f, 69, 6f, 2f, 50, 72, 69, 6e, 74, 53, 74, 72, 65, 61, 6d, 3b]	// Ljava/io/PrintStream;
	# 26 Utf8	length=[0, 13](19), data=[6a, 61, 76, 61, 2f, 69, 6f, 2f, 50, 72, 69, 6e, 74, 53, 74, 72, 65, 61, 6d]	// java/io/PrintStream
	# 27 Utf8	length=[0, 7](7), data=[70, 72, 69, 6e, 74, 6c, 6e]	// println
	# 28 Utf8	length=[0, 15](21), data=[28, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b, 29, 56]	// (Ljava/lang/String;)V

字段:
==================================

方法:
==================================
访问标志: ACC_PUBLIC
方法名: <init>
描述符: ()V
属性: 	Attribute{attributeNameIndex=[0, 9], attributeLength=[0, 0, 0, 1d], info=[0, 1, 0, 1, 0, 0, 0, 5, 2a, b7, 0, 1, b1, 0, 0, 0, 1, 0, a, 0, 0, 0, 6, 0, 1, 0, 0, 0, 1]}

==================================
访问标志: 
方法名: main
描述符: ([Ljava/lang/String;)V
属性: 	Attribute{attributeNameIndex=[0, 9], attributeLength=[0, 0, 0, 25], info=[0, 2, 0, 1, 0, 0, 0, 9, b2, 0, 2, 12, 3, b6, 0, 4, b1, 0, 0, 0, 1, 0, a, 0, 0, 0, a, 0, 2, 0, 0, 0, 3, 0, 8, 0, 4]}

==================================

属性:
Attributes{attributes=[Attribute{attributeNameIndex=[0, d], attributeLength=[0, 0, 0, 2], info=[0, e]}]}

TestClass.java

package com.example;

public class TestClass implements Cloneable{

	// fields
	public byte byteValue = 1;
	public short shortValue = 1;
	public int intValue = 1;
	public float floatValue = 1.01F;
	public double doubleValue = 1.02D;
	public long longValue = 1L;
	public String author = "TTODS";
	public String homePage = "https://blog.csdn.net/qq_44525150";


	// methods
	public byte getByteValue() {
		return byteValue;
	}

	public void setByteValue(byte byteValue) {
		this.byteValue = byteValue;
	}

	public short getShortValue() {
		return shortValue;
	}

	public void setShortValue(short shortValue) {
		this.shortValue = shortValue;
	}

	public int getIntValue() {
		return intValue;
	}

	public void setIntValue(int intValue) {
		this.intValue = intValue;
	}

	public float getFloatValue() {
		return floatValue;
	}

	public void setFloatValue(float floatValue) {
		this.floatValue = floatValue;
	}

	public double getDoubleValue() {
		return doubleValue;
	}

	public void setDoubleValue(double doubleValue) {
		this.doubleValue = doubleValue;
	}

	public long getLongValue() {
		return longValue;
	}

	public void setLongValue(long longValue) {
		this.longValue = longValue;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	@Override
	public String toString() {
		return "TestClass{" +
				"byteValue=" + byteValue +
				", shortValue=" + shortValue +
				", intValue=" + intValue +
				", floatValue=" + floatValue +
				", doubleValue=" + doubleValue +
				", longValue=" + longValue +
				", author='" + author + '\'' +
				'}';
	}
}

TestClass.class文件解析结果

魔数: [ca, fe, ba, be]
次版本: 0
主版本: 52: com/example/TestClass
父类: java/lang/Object
接口:
	#0	java/lang/Cloneable

类访问标志: ACC_PUBLIC,ACC_SUPER
常量池:

	#  1 MethodRef	classIndex=[0, 20](#32), nameAndTypeIndex=[0, 54](#84)	// java/lang/Object.<init>:()V
	#  2 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 55](#85)	// com/example/TestClass.byteValue:B
	#  3 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 56](#86)	// com/example/TestClass.shortValue:S
	#  4 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 57](#87)	// com/example/TestClass.intValue:I
	#  5 Float	data=[3f, 81, 47, ae]	// 1.01F
	#  6 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 58](#88)	// com/example/TestClass.floatValue:F
	#  7 Double	highBytes=[3f, f0, 51, eb], lowBytes=[85, 1e, b8, 52]	// 1.02D

	#  9 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 59](#89)	// com/example/TestClass.doubleValue:D
	# 10 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 5a](#90)	// com/example/TestClass.longValue:J
	# 11 String	stringIndex=[0, 5b](#91)	// TTODS
	# 12 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 5c](#92)	// com/example/TestClass.author:Ljava/lang/String;
	# 13 String	stringIndex=[0, 5d](#93)	// https://blog.csdn.net/qq_44525150
	# 14 FieldRef	classIndex=[0, 1f](#31), nameAndTypeIndex=[0, 5e](#94)	// com/example/TestClass.homePage:Ljava/lang/String;
	# 15 Class	nameIndex=[0, 5f](#95)	// java/lang/StringBuilder
	# 16 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 54](#84)	// java/lang/StringBuilder.<init>:()V
	# 17 String	stringIndex=[0, 60](#96)	// TestClass{byteValue=
	# 18 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 61](#97)	// java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
	# 19 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 62](#98)	// java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
	# 20 String	stringIndex=[0, 63](#99)	// , shortValue=
	# 21 String	stringIndex=[0, 64](#100)	// , intValue=
	# 22 String	stringIndex=[0, 65](#101)	// , floatValue=
	# 23 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 66](#102)	// java/lang/StringBuilder.append:(F)Ljava/lang/StringBuilder;
	# 24 String	stringIndex=[0, 67](#103)	// , doubleValue=
	# 25 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 68](#104)	// java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;
	# 26 String	stringIndex=[0, 69](#105)	// , longValue=
	# 27 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 6a](#106)	// java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
	# 28 String	stringIndex=[0, 6b](#107)	// , author='
	# 29 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 6c](#108)	// java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
	# 30 MethodRef	classIndex=[0, f](#15), nameAndTypeIndex=[0, 6d](#109)	// java/lang/StringBuilder.toString:()Ljava/lang/String;
	# 31 Class	nameIndex=[0, 6e](#110)	// com/example/TestClass
	# 32 Class	nameIndex=[0, 6f](#111)	// java/lang/Object
	# 33 Class	nameIndex=[0, 70](#112)	// java/lang/Cloneable
	# 34 Utf8	length=[0, 9](9), data=[62, 79, 74, 65, 56, 61, 6c, 75, 65]	// byteValue
	# 35 Utf8	length=[0, 1](1), data=[42]	// B
	# 36 Utf8	length=[0, a](10), data=[73, 68, 6f, 72, 74, 56, 61, 6c, 75, 65]	// shortValue
	# 37 Utf8	length=[0, 1](1), data=[53]	// S
	# 38 Utf8	length=[0, 8](8), data=[69, 6e, 74, 56, 61, 6c, 75, 65]	// intValue
	# 39 Utf8	length=[0, 1](1), data=[49]	// I
	# 40 Utf8	length=[0, a](10), data=[66, 6c, 6f, 61, 74, 56, 61, 6c, 75, 65]	// floatValue
	# 41 Utf8	length=[0, 1](1), data=[46]	// F
	# 42 Utf8	length=[0, b](11), data=[64, 6f, 75, 62, 6c, 65, 56, 61, 6c, 75, 65]	// doubleValue
	# 43 Utf8	length=[0, 1](1), data=[44]	// D
	# 44 Utf8	length=[0, 9](9), data=[6c, 6f, 6e, 67, 56, 61, 6c, 75, 65]	// longValue
	# 45 Utf8	length=[0, 1](1), data=[4a]	// J
	# 46 Utf8	length=[0, 6](6), data=[61, 75, 74, 68, 6f, 72]	// author
	# 47 Utf8	length=[0, 12](18), data=[4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b]	// Ljava/lang/String;
	# 48 Utf8	length=[0, 8](8), data=[68, 6f, 6d, 65, 50, 61, 67, 65]	// homePage
	# 49 Utf8	length=[0, 6](6), data=[3c, 69, 6e, 69, 74, 3e]	// <init>
	# 50 Utf8	length=[0, 3](3), data=[28, 29, 56]	// ()V
	# 51 Utf8	length=[0, 4](4), data=[43, 6f, 64, 65]	// Code
	# 52 Utf8	length=[0, f](15), data=[4c, 69, 6e, 65, 4e, 75, 6d, 62, 65, 72, 54, 61, 62, 6c, 65]	// LineNumberTable
	# 53 Utf8	length=[0, c](12), data=[67, 65, 74, 42, 79, 74, 65, 56, 61, 6c, 75, 65]	// getByteValue
	# 54 Utf8	length=[0, 3](3), data=[28, 29, 42]	// ()B
	# 55 Utf8	length=[0, c](12), data=[73, 65, 74, 42, 79, 74, 65, 56, 61, 6c, 75, 65]	// setByteValue
	# 56 Utf8	length=[0, 4](4), data=[28, 42, 29, 56]	// (B)V
	# 57 Utf8	length=[0, d](13), data=[67, 65, 74, 53, 68, 6f, 72, 74, 56, 61, 6c, 75, 65]	// getShortValue
	# 58 Utf8	length=[0, 3](3), data=[28, 29, 53]	// ()S
	# 59 Utf8	length=[0, d](13), data=[73, 65, 74, 53, 68, 6f, 72, 74, 56, 61, 6c, 75, 65]	// setShortValue
	# 60 Utf8	length=[0, 4](4), data=[28, 53, 29, 56]	// (S)V
	# 61 Utf8	length=[0, b](11), data=[67, 65, 74, 49, 6e, 74, 56, 61, 6c, 75, 65]	// getIntValue
	# 62 Utf8	length=[0, 3](3), data=[28, 29, 49]	// ()I
	# 63 Utf8	length=[0, b](11), data=[73, 65, 74, 49, 6e, 74, 56, 61, 6c, 75, 65]	// setIntValue
	# 64 Utf8	length=[0, 4](4), data=[28, 49, 29, 56]	// (I)V
	# 65 Utf8	length=[0, d](13), data=[67, 65, 74, 46, 6c, 6f, 61, 74, 56, 61, 6c, 75, 65]	// getFloatValue
	# 66 Utf8	length=[0, 3](3), data=[28, 29, 46]	// ()F
	# 67 Utf8	length=[0, d](13), data=[73, 65, 74, 46, 6c, 6f, 61, 74, 56, 61, 6c, 75, 65]	// setFloatValue
	# 68 Utf8	length=[0, 4](4), data=[28, 46, 29, 56]	// (F)V
	# 69 Utf8	length=[0, e](14), data=[67, 65, 74, 44, 6f, 75, 62, 6c, 65, 56, 61, 6c, 75, 65]	// getDoubleValue
	# 70 Utf8	length=[0, 3](3), data=[28, 29, 44]	// ()D
	# 71 Utf8	length=[0, e](14), data=[73, 65, 74, 44, 6f, 75, 62, 6c, 65, 56, 61, 6c, 75, 65]	// setDoubleValue
	# 72 Utf8	length=[0, 4](4), data=[28, 44, 29, 56]	// (D)V
	# 73 Utf8	length=[0, c](12), data=[67, 65, 74, 4c, 6f, 6e, 67, 56, 61, 6c, 75, 65]	// getLongValue
	# 74 Utf8	length=[0, 3](3), data=[28, 29, 4a]	// ()J
	# 75 Utf8	length=[0, c](12), data=[73, 65, 74, 4c, 6f, 6e, 67, 56, 61, 6c, 75, 65]	// setLongValue
	# 76 Utf8	length=[0, 4](4), data=[28, 4a, 29, 56]	// (J)V
	# 77 Utf8	length=[0, 9](9), data=[67, 65, 74, 41, 75, 74, 68, 6f, 72]	// getAuthor
	# 78 Utf8	length=[0, 14](20), data=[28, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b]	// ()Ljava/lang/String;
	# 79 Utf8	length=[0, 9](9), data=[73, 65, 74, 41, 75, 74, 68, 6f, 72]	// setAuthor
	# 80 Utf8	length=[0, 15](21), data=[28, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b, 29, 56]	// (Ljava/lang/String;)V
	# 81 Utf8	length=[0, 8](8), data=[74, 6f, 53, 74, 72, 69, 6e, 67]	// toString
	# 82 Utf8	length=[0, a](10), data=[53, 6f, 75, 72, 63, 65, 46, 69, 6c, 65]	// SourceFile
	# 83 Utf8	length=[0, e](14), data=[54, 65, 73, 74, 43, 6c, 61, 73, 73, 2e, 6a, 61, 76, 61]	// TestClass.java
	# 84 NameAndType	nameIndex=[0, 31](#49), descriptorIndex=[0, 32](#50)	// <init>:()V
	# 85 NameAndType	nameIndex=[0, 22](#34), descriptorIndex=[0, 23](#35)	// byteValue:B
	# 86 NameAndType	nameIndex=[0, 24](#36), descriptorIndex=[0, 25](#37)	// shortValue:S
	# 87 NameAndType	nameIndex=[0, 26](#38), descriptorIndex=[0, 27](#39)	// intValue:I
	# 88 NameAndType	nameIndex=[0, 28](#40), descriptorIndex=[0, 29](#41)	// floatValue:F
	# 89 NameAndType	nameIndex=[0, 2a](#42), descriptorIndex=[0, 2b](#43)	// doubleValue:D
	# 90 NameAndType	nameIndex=[0, 2c](#44), descriptorIndex=[0, 2d](#45)	// longValue:J
	# 91 Utf8	length=[0, 5](5), data=[54, 54, 4f, 44, 53]	// TTODS
	# 92 NameAndType	nameIndex=[0, 2e](#46), descriptorIndex=[0, 2f](#47)	// author:Ljava/lang/String;
	# 93 Utf8	length=[0, 21](33), data=[68, 74, 74, 70, 73, 3a, 2f, 2f, 62, 6c, 6f, 67, 2e, 63, 73, 64, 6e, 2e, 6e, 65, 74, 2f, 71, 71, 5f, 34, 34, 35, 32, 35, 31, 35, 30]	// https://blog.csdn.net/qq_44525150
	# 94 NameAndType	nameIndex=[0, 30](#48), descriptorIndex=[0, 2f](#47)	// homePage:Ljava/lang/String;
	# 95 Utf8	length=[0, 17](23), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72]	// java/lang/StringBuilder
	# 96 Utf8	length=[0, 14](20), data=[54, 65, 73, 74, 43, 6c, 61, 73, 73, 7b, 62, 79, 74, 65, 56, 61, 6c, 75, 65, 3d]	// TestClass{byteValue=
	# 97 NameAndType	nameIndex=[0, 71](#113), descriptorIndex=[0, 72](#114)	// append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
	# 98 NameAndType	nameIndex=[0, 71](#113), descriptorIndex=[0, 73](#115)	// append:(I)Ljava/lang/StringBuilder;
	# 99 Utf8	length=[0, d](13), data=[2c, 20, 73, 68, 6f, 72, 74, 56, 61, 6c, 75, 65, 3d]	// , shortValue=
	#100 Utf8	length=[0, b](11), data=[2c, 20, 69, 6e, 74, 56, 61, 6c, 75, 65, 3d]	// , intValue=
	#101 Utf8	length=[0, d](13), data=[2c, 20, 66, 6c, 6f, 61, 74, 56, 61, 6c, 75, 65, 3d]	// , floatValue=
	#102 NameAndType	nameIndex=[0, 71](#113), descriptorIndex=[0, 74](#116)	// append:(F)Ljava/lang/StringBuilder;
	#103 Utf8	length=[0, e](14), data=[2c, 20, 64, 6f, 75, 62, 6c, 65, 56, 61, 6c, 75, 65, 3d]	// , doubleValue=
	#104 NameAndType	nameIndex=[0, 71](#113), descriptorIndex=[0, 75](#117)	// append:(D)Ljava/lang/StringBuilder;
	#105 Utf8	length=[0, c](12), data=[2c, 20, 6c, 6f, 6e, 67, 56, 61, 6c, 75, 65, 3d]	// , longValue=
	#106 NameAndType	nameIndex=[0, 71](#113), descriptorIndex=[0, 76](#118)	// append:(J)Ljava/lang/StringBuilder;
	#107 Utf8	length=[0, a](10), data=[2c, 20, 61, 75, 74, 68, 6f, 72, 3d, 27]	// , author='
	#108 NameAndType	nameIndex=[0, 71](#113), descriptorIndex=[0, 77](#119)	// append:(C)Ljava/lang/StringBuilder;
	#109 NameAndType	nameIndex=[0, 51](#81), descriptorIndex=[0, 4e](#78)	// toString:()Ljava/lang/String;
	#110 Utf8	length=[0, 15](21), data=[63, 6f, 6d, 2f, 65, 78, 61, 6d, 70, 6c, 65, 2f, 54, 65, 73, 74, 43, 6c, 61, 73, 73]	// com/example/TestClass
	#111 Utf8	length=[0, 10](16), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 4f, 62, 6a, 65, 63, 74]	// java/lang/Object
	#112 Utf8	length=[0, 13](19), data=[6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 43, 6c, 6f, 6e, 65, 61, 62, 6c, 65]	// java/lang/Cloneable
	#113 Utf8	length=[0, 6](6), data=[61, 70, 70, 65, 6e, 64]	// append
	#114 Utf8	length=[0, 2d](45), data=[28, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 3b, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72, 3b]	// (Ljava/lang/String;)Ljava/lang/StringBuilder;
	#115 Utf8	length=[0, 1c](28), data=[28, 49, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72, 3b]	// (I)Ljava/lang/StringBuilder;
	#116 Utf8	length=[0, 1c](28), data=[28, 46, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72, 3b]	// (F)Ljava/lang/StringBuilder;
	#117 Utf8	length=[0, 1c](28), data=[28, 44, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72, 3b]	// (D)Ljava/lang/StringBuilder;
	#118 Utf8	length=[0, 1c](28), data=[28, 4a, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72, 3b]	// (J)Ljava/lang/StringBuilder;
	#119 Utf8	length=[0, 1c](28), data=[28, 43, 29, 4c, 6a, 61, 76, 61, 2f, 6c, 61, 6e, 67, 2f, 53, 74, 72, 69, 6e, 67, 42, 75, 69, 6c, 64, 65, 72, 3b]	// (C)Ljava/lang/StringBuilder;

字段:
==================================
访问标志: ACC_PUBLIC
字段名: byteValue
描述符: B

==================================
访问标志: ACC_PUBLIC
字段名: shortValue
描述符: S

==================================
访问标志: ACC_PUBLIC
字段名: intValue
描述符: I

==================================
访问标志: ACC_PUBLIC
字段名: floatValue
描述符: F

==================================
访问标志: ACC_PUBLIC
字段名: doubleValue
描述符: D

==================================
访问标志: ACC_PUBLIC
字段名: longValue
描述符: J

==================================
访问标志: ACC_PUBLIC
字段名: author
描述符: Ljava/lang/String;

==================================
访问标志: ACC_PUBLIC
字段名: homePage
描述符: Ljava/lang/String;

==================================

方法:
==================================
访问标志: ACC_PUBLIC
方法名: <init>
描述符: ()V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 6a], info=[0, 3, 0, 1, 0, 0, 0, 32, 2a, b7, 0, 1, 2a, 4, b5, 0, 2, 2a, 4, b5, 0, 3, 2a, 4, b5, 0, 4, 2a, 12, 5, b5, 0, 6, 2a, 14, 0, 7, b5, 0, 9, 2a, a, b5, 0, a, 2a, 12, b, b5, 0, c, 2a, 12, d, b5, 0, e, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, 26, 0, 9, 0, 0, 0, 3, 0, 4, 0, 6, 0, 9, 0, 7, 0, e, 0, 8, 0, 13, 0, 9, 0, 19, 0, a, 0, 20, 0, b, 0, 25, 0, c, 0, 2b, 0, d]}

==================================
访问标志: ACC_PUBLIC
方法名: getByteValue
描述符: ()B
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 1, 0, 1, 0, 0, 0, 5, 2a, b4, 0, 2, ac, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 12]}

==================================
访问标志: ACC_PUBLIC
方法名: setByteValue
描述符: (B)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 2, 0, 2, 0, 0, 0, 6, 2a, 1b, b5, 0, 2, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 16, 0, 5, 0, 17]}

==================================
访问标志: ACC_PUBLIC
方法名: getShortValue
描述符: ()S
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 1, 0, 1, 0, 0, 0, 5, 2a, b4, 0, 3, ac, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 1a]}

==================================
访问标志: ACC_PUBLIC
方法名: setShortValue
描述符: (S)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 2, 0, 2, 0, 0, 0, 6, 2a, 1b, b5, 0, 3, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 1e, 0, 5, 0, 1f]}

==================================
访问标志: ACC_PUBLIC
方法名: getIntValue
描述符: ()I
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 1, 0, 1, 0, 0, 0, 5, 2a, b4, 0, 4, ac, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 22]}

==================================
访问标志: ACC_PUBLIC
方法名: setIntValue
描述符: (I)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 2, 0, 2, 0, 0, 0, 6, 2a, 1b, b5, 0, 4, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 26, 0, 5, 0, 27]}

==================================
访问标志: ACC_PUBLIC
方法名: getFloatValue
描述符: ()F
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 1, 0, 1, 0, 0, 0, 5, 2a, b4, 0, 6, ae, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 2a]}

==================================
访问标志: ACC_PUBLIC
方法名: setFloatValue
描述符: (F)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 2, 0, 2, 0, 0, 0, 6, 2a, 23, b5, 0, 6, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 2e, 0, 5, 0, 2f]}

==================================
访问标志: ACC_PUBLIC
方法名: getDoubleValue
描述符: ()D
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 2, 0, 1, 0, 0, 0, 5, 2a, b4, 0, 9, af, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 32]}

==================================
访问标志: ACC_PUBLIC
方法名: setDoubleValue
描述符: (D)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 3, 0, 3, 0, 0, 0, 6, 2a, 27, b5, 0, 9, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 36, 0, 5, 0, 37]}

==================================
访问标志: ACC_PUBLIC
方法名: getLongValue
描述符: ()J
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 2, 0, 1, 0, 0, 0, 5, 2a, b4, 0, a, ad, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3a]}

==================================
访问标志: ACC_PUBLIC
方法名: setLongValue
描述符: (J)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 3, 0, 3, 0, 0, 0, 6, 2a, 1f, b5, 0, a, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 3e, 0, 5, 0, 3f]}

==================================
访问标志: ACC_PUBLIC
方法名: getAuthor
描述符: ()Ljava/lang/String;
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 1d], info=[0, 1, 0, 1, 0, 0, 0, 5, 2a, b4, 0, c, b0, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 42]}

==================================
访问标志: ACC_PUBLIC
方法名: setAuthor
描述符: (Ljava/lang/String;)V
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 22], info=[0, 2, 0, 2, 0, 0, 0, 6, 2a, 2b, b5, 0, c, b1, 0, 0, 0, 1, 0, 34, 0, 0, 0, a, 0, 2, 0, 0, 0, 46, 0, 5, 0, 47]}

==================================
访问标志: ACC_PUBLIC
方法名: toString
描述符: ()Ljava/lang/String;
属性: 	Attribute{attributeNameIndex=[0, 33], attributeLength=[0, 0, 0, 81], info=[0, 3, 0, 1, 0, 0, 0, 69, bb, 0, f, 59, b7, 0, 10, 12, 11, b6, 0, 12, 2a, b4, 0, 2, b6, 0, 13, 12, 14, b6, 0, 12, 2a, b4, 0, 3, b6, 0, 13, 12, 15, b6, 0, 12, 2a, b4, 0, 4, b6, 0, 13, 12, 16, b6, 0, 12, 2a, b4, 0, 6, b6, 0, 17, 12, 18, b6, 0, 12, 2a, b4, 0, 9, b6, 0, 19, 12, 1a, b6, 0, 12, 2a, b4, 0, a, b6, 0, 1b, 12, 1c, b6, 0, 12, 2a, b4, 0, c, b6, 0, 12, 10, 27, b6, 0, 1d, 10, 7d, b6, 0, 1d, b6, 0, 1e, b0, 0, 0, 0, 1, 0, 34, 0, 0, 0, 6, 0, 1, 0, 0, 0, 4b]}

==================================

属性:
Attributes{attributes=[Attribute{attributeNameIndex=[0, 52], attributeLength=[0, 0, 0, 2], info=[0, 53]}]}

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值