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

背景

[Java] 如何理解 class 文件中字段的 descriptor? 一文中,我们已经介绍了 <math xmlns="http://www.w3.org/1998/Math/MathML"> class \text{class} </math>class 文件中字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 的结构,一些示例值如下

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

我们能否通过解析这个 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 来得到原始的类型信息呢?即类似下表这样的效果 ⬇️

<math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor ➡️ 原始的类型信息
I int
Ljava/lang/Object; java.lang.Object
[I int[]
[[[Ljava/util/List; java.util.List[][][]

先明确一个问题,因为字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 中不包含泛型中的类型信息(例如 List<Integer> 中的 Integer 不会保存在字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 里),所以我们无法通过字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor找到泛型中的类型信息(但是可以找到 java.util.List 这个类型自身)。根据字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 的构造规则(细节可以参考 [Java] 如何理解 class 文件中字段的 descriptor? 一文),看起来可以从字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 中找到字段的完整类型信息。我们来实战一下。

正文

根据字段的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 的构造规则(细节可以参考 [Java] 如何理解 class 文件中字段的 descriptor? 一文),可以写出对应的 <math xmlns="http://www.w3.org/1998/Math/MathML"> Parser \text{Parser} </math>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);
        }
    }
}

编译和运行

使用如下的命令可以编译 <math xmlns="http://www.w3.org/1998/Math/MathML"> FieldDescriptorParser.java \text{FieldDescriptorParser.java} </math>FieldDescriptorParser.java

bash 复制代码
javac FieldDescriptorParser.java

编译之后,当前目录会多出一个名为 <math xmlns="http://www.w3.org/1998/Math/MathML"> FieldDescriptorParser.class \text{FieldDescriptorParser.class} </math>FieldDescriptorParser.class 的文件。下方的命令可以运行 <math xmlns="http://www.w3.org/1998/Math/MathML"> FieldDescriptorParser \text{FieldDescriptorParser} </math>FieldDescriptorParser 类中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> main \text{main} </math>main 方法

bash 复制代码
java FieldDescriptorParser

该命令的输出如下

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

说明我们的使用方法不对(至少应该提供一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor 作为参数)。

为了验证各种情况,我使用了很多 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>descriptor ⬇️ (这里的 <math xmlns="http://www.w3.org/1998/Math/MathML"> descriptor \text{descriptor} </math>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[][][]]

解析结果符合预期

其他

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

参考资料

相关推荐
ch.ju6 小时前
Java程序设计(第3版)第四章——成员方法
java·开发语言
椰椰椰耶6 小时前
[SpringCloud][8]Spring Cloud LoadBanlancer快速上手以及LoadBalancer原理
后端·spring·spring cloud
Dicky-_-zhang6 小时前
微服务安全防护实战:OAuth2与JWT鉴权
java·jvm
牙牙学语的阿猿6 小时前
sentinel创建规则时的坑
java·开发语言·sentinel
未若君雅裁6 小时前
微服务限流实战:Nginx 漏桶与网关令牌桶
java·nginx·微服务
罗超驿7 小时前
1.JavaEE初阶学习安排+介绍计算机是如何工作的
java·学习·java-ee
超梦dasgg7 小时前
Java 生产环境 JVM 调优实战
java·开发语言·jvm
Genlt7 小时前
Docker 镜像与 Dockerfile 基础指南:从编写到管理
后端
phltxy7 小时前
RabbitMQ 工作模式与Java原生客户端案例
java·rabbitmq·java-rabbitmq