[Java] 自己写程序,来解析方法的 descriptor

背景

Java 如何理解 class 文件中方法的 descriptor? 一文中,我们已经了解了 class\text{class} class 文件中方法的 descriptor\text{descriptor} descriptor 的结构,一些示例值如下

  • ()I
  • (Ljava/lang/Object;)Z
  • (JI)V
  • (Ljava/lang/Object;)Ljava/util/stream/Stream;
  • ([Ljava/lang/Object;)Ljava/util/stream/Stream;
  • ([[[I)V
  • ([[[Ljava/lang/String;)V

我们能否通过解析这个 descriptor\text{descriptor} descriptor 来得到原始的类型信息呢?即类似下表这样的效果 ⬇️

descriptor\text{descriptor} descriptor 入参的类型信息 返回值的类型信息
()I (无) int
(Ljava/lang/Object;)Z java.lang.Object boolean
(JI)V long, int void
(Ljava/lang/Object;)Ljava/util/stream/Stream; java.lang.Object java.util.stream.Stream
([Ljava/lang/Object;)Ljava/util/stream/Stream; java.lang.Object[] java.util.stream.Stream
([[[[I)V int[][][] void
([[[Ljava/lang/String;)V java.lang.String[][][] void

先明确一个问题,因为方法的 descriptor\text{descriptor} descriptor 中不包含泛型中的类型信息(例子:如果返回值的类型是 java.util.List<Integer> ,那么其中的 Integer 不会保存在方法的 descriptor\text{descriptor} descriptor 里),所以我们无法通过方法的 descriptor\text{descriptor} descriptor 找到泛型中的类型信息(但是可以找到 java.util.List 这个类型自身)。根据方法的 descriptor\text{descriptor} descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中方法的 descriptor? 一文),看起来可以从方法的 descriptor\text{descriptor} descriptor 中找到方法的完整类型信息(即各个入参的类型信息以及返回值的类型信息)。我们来实战一下。

正文

Java 自己写程序,来解析字段的 descriptor 一文中,我写了一个 java\text{java} java 程序,它可以解析字段的 descriptor\text{descriptor} descriptor。在此基础上,我们根据方法的 descriptor\text{descriptor} descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中方法的 descriptor? 一文),可以写出对应的 Parser\text{Parser} Parser,写程序的细节就不赘述了,代码应该还算比较直白。具体的代码如下

代码

请将以下代码保存为 MethodDescriptorParser.java\text{MethodDescriptorParser.java} MethodDescriptorParser.java

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MethodDescriptorParser {
    private static final char LEFT_PARENTHESIS = '(';
    private static final char RIGHT_PARENTHESIS = ')';
    private static final char CLASS_TYPE_POSTFIX = ';';
    private static final char ARRAY_TYPE_PREFIX = '[';
    private static final char CLASS_TYPE_PREFIX = 'L';

    private static final Map<String, String> baseTypes = Map.ofEntries(
            Map.entry("B", "byte"),
            Map.entry("C", "char"),
            Map.entry("D", "double"),
            Map.entry("F", "float"),
            Map.entry("I", "int"),
            Map.entry("J", "long"),
            Map.entry("S", "short"),
            Map.entry("Z", "boolean")
    );

    private int currIndex;

    public ParseResult parse(String descriptor) {
        currIndex = 0;
        if (descriptor.charAt(0) != LEFT_PARENTHESIS) {
            throw new IllegalArgumentException("Bad descriptor: " + descriptor);
        }

        // Drop '('
        currIndex++;

        List<String> parameterTypes = parseParameterDescriptors(descriptor);

        // Drop ')'
        currIndex++;

        String returnType = parseReturnDescriptor(descriptor);
        return new ParseResult(parameterTypes, returnType);
    }

    private List<String> parseParameterDescriptors(String descriptor) {
        List<String> parameterTypes = new ArrayList<>();
        while (descriptor.charAt(currIndex) != RIGHT_PARENTHESIS) {
            parameterTypes.add(parseParameterDescriptor(descriptor));
        }
        return parameterTypes;
    }

    private String parseParameterDescriptor(String descriptor) {
        return parseFieldType(descriptor);
    }

    private String parseFieldType(String descriptor) {
        char start = descriptor.charAt(currIndex);

        if (start == ARRAY_TYPE_PREFIX) {
            currIndex++;
            String componentName = parseFieldType(descriptor);
            return componentName + "[]";
        }

        if (start == CLASS_TYPE_PREFIX) {
            int endIndex = currIndex + 1;
            while (descriptor.charAt(endIndex) != CLASS_TYPE_POSTFIX) {
                endIndex++;
            }

            // Drop 'L' and ';' and convert.
            // Example: "Ljava/lang/Object;" -> "java.lang.Object"
            String className = descriptor.substring(currIndex + 1, endIndex).replace('/', '.');
            currIndex = endIndex + 1;
            return className;
        }

        String baseTypeName = baseTypes.get(descriptor.substring(currIndex, currIndex + 1));
        currIndex++;
        return baseTypeName;
    }

    private String parseReturnDescriptor(String descriptor) {
        if (descriptor.charAt(currIndex) == 'V') {
            return "void";
        }
        return parseFieldType(descriptor);
    }

    public record ParseResult(List<String> parameterTypes, String returnType) {
    }

    private void printUsage() {
        System.out.println("Usage: java MethodDescriptorParser [methodDescriptor ...]");
    }

    public static void main(String[] args) {
        MethodDescriptorParser parser = new MethodDescriptorParser();
        if (args.length == 0) {
            parser.printUsage();
            return;
        }

        for (String arg : args) {
            System.out.println("Parse result for method descriptor: " + arg);

            ParseResult parseResult = parser.parse(arg);
            System.out.println("Parameter types: " + String.join(", ", parseResult.parameterTypes));
            System.out.println("Return type: " + parseResult.returnType);
        }
    }
}

编译和运行

使用如下的命令可以编译 MethodDescriptorParser.java\text{MethodDescriptorParser.java} MethodDescriptorParser.java

bash 复制代码
javac MethodDescriptorParser.java

编译之后,当前目录会多出一个名为 MethodDescriptorParser.class\text{MethodDescriptorParser.class} MethodDescriptorParser.class 的文件。下方的命令可以运行 MethodDescriptorParser\text{MethodDescriptorParser} MethodDescriptorParser 类中的 main\text{main} main 方法

bash 复制代码
java MethodDescriptorParser

该命令的输出如下

text 复制代码
Usage: java MethodDescriptorParser [methodDescriptor ...]

说明我们的使用方法不对(至少应该提供一个 descriptor\text{descriptor} descriptor 作为参数)。

为了验证各种情况,我使用了很多 descriptor\text{descriptor} descriptor ⬇️ (这里的 descriptor\text{descriptor} descriptor 都来自 Java 如何理解 class 文件中方法的 descriptor? 一文)

bash 复制代码
java MethodDescriptorParser '()I' '(Ljava/lang/Object;)Z' '(JI)V' \
'(Ljava/lang/Object;)Ljava/util/stream/Stream;' \
'([Ljava/lang/Object;)Ljava/util/stream/Stream;' '([[[I)V' \
'([[[Ljava/lang/String;)V'

这个命令的运行结果如下 ⬇️

text 复制代码
Parse result for method descriptor: ()I
Parameter types: 
Return type: int
Parse result for method descriptor: (Ljava/lang/Object;)Z
Parameter types: java.lang.Object
Return type: boolean
Parse result for method descriptor: (JI)V
Parameter types: long, int
Return type: void
Parse result for method descriptor: (Ljava/lang/Object;)Ljava/util/stream/Stream;
Parameter types: java.lang.Object
Return type: java.util.stream.Stream
Parse result for method descriptor: ([Ljava/lang/Object;)Ljava/util/stream/Stream;
Parameter types: java.lang.Object[]
Return type: java.util.stream.Stream
Parse result for method descriptor: ([[[I)V
Parameter types: int[][][]
Return type: void
Parse result for method descriptor: ([[[Ljava/lang/String;)V
Parameter types: java.lang.String[][][]
Return type: void

可以整理成如下的表格

descriptor\text{descriptor} descriptor 入参的类型信息 返回值的类型信息
()I (无) int
(Ljava/lang/Object;)Z java.lang.Object boolean
(JI)V long, int void
(Ljava/lang/Object;)Ljava/util/stream/Stream; java.lang.Object java.util.stream.Stream
([Ljava/lang/Object;)Ljava/util/stream/Stream; java.lang.Object[] java.util.stream.Stream
([[[[I)V int[][][] void
([[[Ljava/lang/String;)V java.lang.String[][][] void

解析结果符合预期

参考资料

相关推荐
Yang96111 小时前
0.5 米超短盲区!鼎讯信通 GO-50PRO 光时域反射仪科普
开发语言·后端·golang
红辣椒...1 小时前
codex+第三方模型
java·服务器·前端
一个做软件开发的牛马1 小时前
Java 继承与多态:从"是什么"到"能做什么"的设计思维
java·后端
jump6801 小时前
java的配置对象@Configuration
后端
不懂的浪漫2 小时前
05|Netty ByteBuf 源码分析:为什么不用 Java ByteBuffer
java·netty
wapicn992 小时前
API接口调试笔记:从注册到第一个数据返回,全流程详解
java·开发语言·python·lua
程序员阿明2 小时前
flowable集成flowable及其运行示例spring boot后端
java·spring boot·后端
代码不停2 小时前
Spring IoC&DI
java·后端·spring
我是一颗柠檬2 小时前
【Redis】数据类型详解Day2(2026年)
数据库·redis·后端·缓存