背景
在 [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 的结构,一些示例值如下
ILjava/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(在遵守法律的前提下),不用告知我。