背景
在日常开发过程中,我们有时候需要查看 class 文件的内容。如果我们对 class 文件的结构有基本的了解,那么就会事半功倍。由于这个话题很大,而且我自己的水平也有限,所以每次只写一个很小的主题。本文的主题是理解 class 文件中方法的 descriptor。
下表中列举了 java.lang.Object 中的一些方法和对应的 descriptor。
java.lang.Object 中的哪个方法 |
java 代码中的类型 | 对应的 descriptor |
|---|---|---|
hashCode() 方法 |
没有入参 返回值是 int 类型 |
()I |
equals(Object)方法 |
入参是 java.lang.Object 类型 返回值是 boolean 类型 |
(Ljava/lang/Object;)Z |
wait(long, int) 方法 |
入参分别是 long 和 int 类型 返回值是 void 类型 |
(JI)V |
下表中列举了 java.util.stream.Stream<T> 中的一些方法和对应的 descriptor。
java.util.stream.Stream<T> 中的哪个方法 |
java 代码中的类型 | 对应的 descriptor |
|---|---|---|
of(T) 方法 |
入参是 T 类型 返回值是 java.util.stream.Stream<T> 类型 |
(Ljava/lang/Object;)Ljava/util/stream/Stream; |
of(T...) 方法 |
入参是 T... 类型(可以视为 T 的数组) 返回值是 java.util.stream.Stream<T> 类型 |
([Ljava/lang/Object;)Ljava/util/stream/Stream; |
读完本文后,您会了解对应的转化遵循怎样的逻辑。
要点
- 方法的 descriptor 体现了方法的类型信息,但是泛型中的类型信息不会记录在其中(假设返回值是
List<Integer>,那么其中的Integer不会记录在 descriptor 里) - 方法的 descriptor 建立在字段 descriptor 的基础上
- 字段的 descriptor 共有三种情况 ⬇️
- 8 个基本类型
- (除了数组外的)引用类型
- 数组
- 字段的 descriptor 共有三种情况 ⬇️
示例值
| 方法的入参的类型 | 方法的返回值的类型 | 对应的 descriptor |
|---|---|---|
| (无) | void |
()V |
int |
void |
(I)V |
int, int |
int |
(II)I |
java.lang.Object |
java.lang.String |
(Ljava/lang/Object;)Ljava/lang/String; |
int[] |
void |
([I)V |
int[] |
int[] |
([I)[I |
java.lang.String[] |
java.lang.String[] |
([Ljava/lang/String;)[Ljava/lang/String; |
正文
理论知识
Java 如何理解 class 文件中字段的 descriptor? 一文中介绍了字段的 descriptor。在此基础上,我们来探索方法的 descriptor。
The Java® Virtual Machine Specification 中的 4.6. Methods 小节详细介绍了 class 文件中 method_info 的结构,它的开头是这样的 ⬇️

descriptor,字面意思是描述符,方法的类型信息保存在其中。在 The Java® Virtual Machine Specification 中的 4.3.3. Method Descriptors 小节有关于方法的 descriptor 的描述 ⬇️

可见, MethodDescriptor 是由以下内容组成的
- 1 个
(字符 - n ( n≥0)个 ParameterDescriptor (ParameterDescriptor 中的内容是 FieldType)
- 1 个
)字符 - 1 个 ReturnDescriptor (ReturnDescriptor 中的内容是 FieldType 或者 VoidDescriptor)
FieldType 中的内容共有 3 种情况 ⬇️

Java 如何理解 class 文件中字段的 descriptor? 一文中有更多相关内容,这里就不赘述了。
而 VoidDescriptor 其实就是一个 V 字符。
现在已经有了足够的理论知识,让我们写点代码来实战吧。
用代码来实战
请将以下代码保存为 Main.java
java
public abstract class Main {
abstract void method1();
abstract void method2(int a);
abstract int method3(int a, int b);
abstract String method4(Object o);
abstract void method5(int[] array);
abstract int[] method6(int[] array);
abstract String[] method7(String[] strings);
}
编译
使用以下命令可以编译 Main.java
bash
javac Main.java
编译之后,会看到当前目录下多了一个 Main.class 文件 ⬇️
查看 class 文件的内容
使用以下命令可以查看 Main.class 文件的详细内容
bash
javap -v -p Main
结果如下(我略去了开头几行)
text
public abstract class Main
minor version: 0
major version: 65
flags: (0x0421) ACC_PUBLIC, ACC_SUPER, ACC_ABSTRACT
this_class: #7 // Main
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 8, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Class #8 // Main
#8 = Utf8 Main
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 method1
#12 = Utf8 method2
#13 = Utf8 (I)V
#14 = Utf8 method3
#15 = Utf8 (II)I
#16 = Utf8 method4
#17 = Utf8 (Ljava/lang/Object;)Ljava/lang/String;
#18 = Utf8 method5
#19 = Utf8 ([I)V
#20 = Utf8 method6
#21 = Utf8 ([I)[I
#22 = Utf8 method7
#23 = Utf8 ([Ljava/lang/String;)[Ljava/lang/String;
#24 = Utf8 SourceFile
#25 = Utf8 Main.java
{
public Main();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
abstract void method1();
descriptor: ()V
flags: (0x0400) ACC_ABSTRACT
abstract void method2(int);
descriptor: (I)V
flags: (0x0400) ACC_ABSTRACT
abstract int method3(int, int);
descriptor: (II)I
flags: (0x0400) ACC_ABSTRACT
abstract java.lang.String method4(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0400) ACC_ABSTRACT
abstract void method5(int[]);
descriptor: ([I)V
flags: (0x0400) ACC_ABSTRACT
abstract int[] method6(int[]);
descriptor: ([I)[I
flags: (0x0400) ACC_ABSTRACT
abstract java.lang.String[] method7(java.lang.String[]);
descriptor: ([Ljava/lang/String;)[Ljava/lang/String;
flags: (0x0400) ACC_ABSTRACT
}
SourceFile: "Main.java"
从中可以看到 method1 ~ method7 的 descriptor,结果整理如下 ⬇️
| 方法的名称 | 方法的入参的类型 | 方法的返回值的类型 | 对应的 descriptor |
|---|---|---|---|
| method1 | (无) | void |
()V |
| method2 | int |
void |
(I)V |
| method3 | int, int |
int |
(II)I |
| method4 | java.lang.Object |
java.lang.String |
(Ljava/lang/Object;)Ljava/lang/String; |
| method5 | int[] |
void |
([I)V |
| method6 | int[] |
int[] |
([I)[I |
| method7 | java.lang.String[] |
java.lang.String[] |
([Ljava/lang/String;)[Ljava/lang/String; |