背景
在 Java 如何理解 class 文件中字段的 descriptor? 一文中,我们已经介绍了 class 文件中字段的 descriptor 的结构,一些示例值如下
ILjava/lang/Object;[I[[[java/util/List;
我们能否通过解析这个 descriptor 来得到原始的类型信息呢?即类似下表这样的效果 ⬇️
| descriptor | ➡️ 原始的类型信息 |
|---|---|
I |
int |
Ljava/lang/Object; |
java.lang.Object |
[I |
int[] |
[[[Ljava/util/List; |
java.util.List[][][] |
先明确一个问题,因为字段的 descriptor 中不包含泛型中的类型信息(例如 List<Integer> 中的 Integer 不会保存在字段的 descriptor 里),所以我们无法通过字段的 descriptor找到泛型中的类型信息(但是可以找到 java.util.List 这个类型自身)。根据字段的 descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中字段的 descriptor? 一文),看起来可以从字段的 descriptor 中找到字段的完整类型信息。我们来实战一下。
正文
根据字段的 descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中字段的 descriptor? 一文),可以写出对应的 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
bash
javac FieldDescriptorParser.java
编译之后,当前目录会多出一个名为 FieldDescriptorParser.class 的文件。下方的命令可以运行 FieldDescriptorParser 类中的 main 方法
bash
java FieldDescriptorParser
该命令的输出如下
text
Usage: java FieldDescriptorParser [fieldDescriptor ...]
说明我们的使用方法不对(至少应该提供一个 descriptor 作为参数)。
为了验证各种情况,我使用了很多 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 的核心逻辑是我自己写的(在 IntelliJ IDEA (Community Edition) 中写的)。我借助 trae 完成了 printUsage 方法的逻辑(这个方法重要性低)。所以整体而言,没有版权问题,读者朋友可以随意使用 FieldDescriptorParser.java(在遵守法律的前提下),不用告知我。