[Java] 自己写程序,来解析字段的 descriptor

背景

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

  • I
  • Ljava/lang/Object;
  • [I
  • [[[java/util/List;

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

descriptor \text{descriptor} descriptor ➡️ 原始的类型信息
I int
Ljava/lang/Object; java.lang.Object
[I int[]
[[[Ljava/util/List; java.util.List[][][]

先明确一个问题,因为字段的 descriptor \text{descriptor} descriptor 中不包含泛型中的类型信息(例如 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 中找到字段的完整类型信息。我们来实战一下。

正文

根据字段的 descriptor \text{descriptor} descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中字段的 descriptor? 一文),可以写出对应的 Parser \text{Parser} Parser,写程序的细节就不赘述了,代码应该还算比较直白。具体的代码如下

代码

请将以下代码保存为 FieldDescriptorParser.java

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

public class FieldDescriptorParser {

    public interface FieldType {
        String desc();
    }

    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 static final String ARRAY_TYPE_PREFIX = "[";
    private static final String CLASS_TYPE_PREFIX = "L";
    private static final String CLASS_TYPE_POSTFIX = ";";

    public FieldType parse(String descriptor) {
        if (descriptor.startsWith(ARRAY_TYPE_PREFIX)) {
            return parseArrayType(descriptor);
        }
        if (descriptor.startsWith(CLASS_TYPE_PREFIX)) {
            return parseClassType(descriptor);
        }

        return parseBaseType(descriptor);
    }

    private FieldType parseArrayType(String descriptor) {
        FieldType componentType = parse(descriptor.substring(1));
        return () -> componentType.desc() + "[]";
    }

    private FieldType parseClassType(String descriptor) {
        if (!descriptor.endsWith(CLASS_TYPE_POSTFIX)) {
            throw new IllegalArgumentException("Bad descriptor: " + descriptor);
        }
        // drop 'L' and ';' example: Ljava/lang/Object; ====> java/lang/Object
        String internalName = descriptor.substring(1, descriptor.length() - 1);
        // example: java/lang/Object ====> java.lang.Object
        String originalName = internalName.replace('/', '.');
        return () -> originalName;
    }

    private FieldType parseBaseType(String descriptor) {
        if (baseTypes.containsKey(descriptor)) {
            return () -> baseTypes.get(descriptor);
        }
        throw new IllegalArgumentException("Bad descriptor: " + descriptor);
    }

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

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

        for (String arg : args) {
            FieldType fieldType = parser.parse(arg);
            String message = String.format("For field descriptor: [%s], its original type is: [%s]",
                    arg, fieldType.desc());
            System.out.println(message);
        }
    }
}

编译和运行

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

bash 复制代码
javac FieldDescriptorParser.java

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

bash 复制代码
java FieldDescriptorParser

该命令的输出如下

text 复制代码
Usage: java FieldDescriptorParser [fieldDescriptor ...]

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

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

bash 复制代码
java FieldDescriptorParser 'B' 'C' 'D' 'F' 'I' 'J' 'S' 'Z' '[I' \
'[[D' '[[[[J' 'Ljava/lang/Boolean;' 'Ljava/lang/Long;' \
'Ljava/lang/Object;' 'Ljava/util/List;' 'Ljava/util/Map;' \
'Ljava/util/List;' 'Ljava/lang/Void;' '[Ljava/lang/Boolean;' \
'[[[Ljava/lang/Integer;'

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

text 复制代码
For field descriptor: [B], its original type is: [byte]
For field descriptor: [C], its original type is: [char]
For field descriptor: [D], its original type is: [double]
For field descriptor: [F], its original type is: [float]
For field descriptor: [I], its original type is: [int]
For field descriptor: [J], its original type is: [long]
For field descriptor: [S], its original type is: [short]
For field descriptor: [Z], its original type is: [boolean]
For field descriptor: [[I], its original type is: [int[]]
For field descriptor: [[[D], its original type is: [double[][]]
For field descriptor: [[[[[J], its original type is: [long[][][][]]
For field descriptor: [Ljava/lang/Boolean;], its original type is: [java.lang.Boolean]
For field descriptor: [Ljava/lang/Long;], its original type is: [java.lang.Long]
For field descriptor: [Ljava/lang/Object;], its original type is: [java.lang.Object]
For field descriptor: [Ljava/util/List;], its original type is: [java.util.List]
For field descriptor: [Ljava/util/Map;], its original type is: [java.util.Map]
For field descriptor: [Ljava/util/List;], its original type is: [java.util.List]
For field descriptor: [Ljava/lang/Void;], its original type is: [java.lang.Void]
For field descriptor: [[Ljava/lang/Boolean;], its original type is: [java.lang.Boolean[]]
For field descriptor: [[[[Ljava/lang/Integer;], its original type is: [java.lang.Integer[][][]]

解析结果符合预期

其他

FieldDescriptorParser.java \text{FieldDescriptorParser.java} FieldDescriptorParser.java 的核心逻辑是我自己写的(在 IntelliJ IDEA (Community Edition) 中写的)。我借助 trae 完成了 printUsage \text{printUsage} printUsage 方法的逻辑(这个方法重要性低)。所以整体而言,没有版权问题,读者朋友可以随意使用 FieldDescriptorParser.java \text{FieldDescriptorParser.java} FieldDescriptorParser.java(在遵守法律的前提下),不用告知我。

参考资料

相关推荐
阿俊-全栈开发12 分钟前
LikeShop 多商户 Java 版商城微服务模块拆分与部署架构演进实践
java·微服务·架构·likeshop开源商城·多商户源码
Wang's Blog36 分钟前
Java 项目实战:交友项目 - 联系人列表
java·开发语言·交友
我命由我1234540 分钟前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
香蕉大香蕉(06敬畏市场)1 小时前
MySQL 单点详解(01)
java·数据库·mysql·面试·博客·实习
小小张说故事2 小时前
Python 的 str 为什么设计成不可变?拆开字符串驻留、字符串池,以及 += 拼接到底慢在哪
后端·python
LaughingZhu2 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion
AI架构师之家2 小时前
我把 Hermes 这匹"马"养了两周,它干了一件让我有点发毛的事
后端
dpharness2 小时前
块协议用了两周,我把九个坑按顺序记下来了
后端
niucloud-admin2 小时前
NIUCLOUD PHP SAAS上门家政使用文档:服务保障
java·开发语言·php
加宝的起点雷达2 小时前
跑了两周的信号日报,我把降级链改了四次
人工智能·后端