[Java] 如何理解 class 文件中方法的 descriptor?

背景

在日常开发过程中,我们有时候需要查看 class\text{class} class 文件的内容。如果我们对 class\text{class} class 文件的结构有基本的了解,那么就会事半功倍。由于这个话题很大,而且我自己的水平也有限,所以每次只写一个很小的主题。本文的主题是理解 class\text{class} class 文件中方法的 descriptor\text{descriptor} descriptor。

下表中列举了 java.lang.Object 中的一些方法和对应的 descriptor\text{descriptor} descriptor。

java.lang.Object 中的哪个方法 java\text{java} java 代码中的类型 对应的 descriptor\text{descriptor} descriptor
hashCode() 方法 没有入参 返回值是 int 类型 ()I
equals(Object)方法 入参是 java.lang.Object 类型 返回值是 boolean 类型 (Ljava/lang/Object;)Z
wait(long, int) 方法 入参分别是 longint 类型 返回值是 void 类型 (JI)V

下表中列举了 java.util.stream.Stream<T> 中的一些方法和对应的 descriptor\text{descriptor} descriptor。

java.util.stream.Stream<T> 中的哪个方法 java\text{java} java 代码中的类型 对应的 descriptor\text{descriptor} 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\text{descriptor} descriptor 体现了方法的类型信息,但是泛型中的类型信息不会记录在其中(假设返回值是 List<Integer>,那么其中的 Integer 不会记录在 descriptor\text{descriptor} descriptor 里)
  • 方法的 descriptor\text{descriptor} descriptor 建立在字段 descriptor\text{descriptor} descriptor 的基础上
    • 字段的 descriptor\text{descriptor} descriptor 共有三种情况 ⬇️
      • 8\text{8} 8 个基本类型
      • (除了数组外的)引用类型
      • 数组

示例值

方法的入参的类型 方法的返回值的类型 对应的 descriptor\text{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\text{descriptor} descriptor。在此基础上,我们来探索方法的 descriptor\text{descriptor} descriptor。

The Java® Virtual Machine Specification 中的 4.6. Methods 小节详细介绍了 class\text{class} class 文件中 method_info\text{method\_info} method_info 的结构,它的开头是这样的 ⬇️

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

可见, MethodDescriptor\text{MethodDescriptor} MethodDescriptor 是由以下内容组成的

FieldType 中的内容共有 33 3 种情况 ⬇️

Java 如何理解 class 文件中字段的 descriptor? 一文中有更多相关内容,这里就不赘述了。

VoidDescriptor 其实就是一个 V\text{V} V 字符。

现在已经有了足够的理论知识,让我们写点代码来实战吧。

用代码来实战

请将以下代码保存为 Main.java\text{Main.java} 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\text{Main.java} Main.java

bash 复制代码
javac Main.java

编译之后,会看到当前目录下多了一个 Main.class\text{Main.class} Main.class 文件 ⬇️

查看 class\text{class} class 文件的内容

使用以下命令可以查看 Main.class\text{Main.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\text{method1} method1 ~ method7\text{method7} method7 的 descriptor\text{descriptor} descriptor,结果整理如下 ⬇️

方法的名称 方法的入参的类型 方法的返回值的类型 对应的 descriptor\text{descriptor} descriptor
method1\text{method1} method1 (无) void ()V
method2\text{method2} method2 int void (I)V
method3\text{method3} method3 int, int int (II)I
method4\text{method4} method4 java.lang.Object java.lang.String (Ljava/lang/Object;)Ljava/lang/String;
method5\text{method5} method5 int[] void ([I)V
method6\text{method6} method6 int[] int[] ([I)[I
method7\text{method7} method7 java.lang.String[] java.lang.String[] ([Ljava/lang/String;)[Ljava/lang/String;

参考资料

相关推荐
牡丹雅忻112 分钟前
AES 加密模式演进:从 ECB、CBC 到 GCM 的 C# 深度实践
java·开发语言·c#
学者猫头鹰21 分钟前
Java 8 核心新特性
java
Python私教31 分钟前
Django 6.1 RC1 实测:FETCH_PEERS 两条 SQL 解决 N+1,select_related 还需要吗?
后端·python·django
大侠在此在在此39 分钟前
教你用最简单的数学自底向上地高效学习SQL注入
后端
冰心孤城44 分钟前
Excel: xls与xlsx格式转换排坑指南
java·前端·excel
自强的小白1 小时前
maven高级(聚合和私服)以及私服的上传和下载
java·maven
Python私教1 小时前
Django 6.0 自带 Tasks 到底能不能替代 Celery?跑完 3 组后台任务后我有答案了
后端·python·django
唐青枫1 小时前
Java ReentrantLock 实战详解:比 synchronized 更灵活的可重入锁
java
看昭奚恤哭2 小时前
ontainer App】Container App无法从Container Registries 拉取镜像 - 报错 Forbidden
后端·python·flask
ShiXZ2132 小时前
Java 8 Stream API 实用技巧详解:从入门到精通
java·开发语言