背景
在 [Java] 如何理解 class 文件中方法的 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
我们能否通过解析这个 来得到原始的类型信息呢?即类似下表这样的效果 ⬇️
| 入参的类型信息 | 返回值的类型信息 | |
|---|---|---|
()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 |
先明确一个问题,因为方法的 中不包含泛型中的类型信息(例子:如果返回值的类型是 java.util.List<Integer> ,那么其中的 Integer 不会保存在方法的 里),所以我们无法通过方法的 找到泛型中的类型信息(但是可以找到 java.util.List 这个类型自身)。根据方法的 的构造规则(细节可以参考 [Java] 如何理解 class 文件中方法的 descriptor? 一文),看起来可以从方法的 中找到方法的完整类型信息(即各个入参的类型信息以及返回值的类型信息)。我们来实战一下。
正文
在 [Java] 自己写程序,来解析字段的 descriptor 一文中,我写了一个 程序,它可以解析字段的 。在此基础上,我们根据方法的 的构造规则(细节可以参考 [Java] 如何理解 class 文件中方法的 descriptor? 一文),可以写出对应的 ,写程序的细节就不赘述了,代码应该还算比较直白。具体的代码如下
代码
请将以下代码保存为
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) {
public void display() {
if (parameterTypes.isEmpty()) {
System.out.println("Parameter types: (EMPTY)");
} else {
System.out.println("Parameter types: " + String.join(", ", parameterTypes));
}
System.out.println("Return type: " + 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("=".repeat(50));
System.out.println("Parse result for method descriptor: " + arg);
parser.parse(arg).display();
}
}
}
简要的类图如下 ⬇️
编译和运行
使用如下的命令可以编译
javac MethodDescriptorParser.java
编译之后,当前目录会多出一个名为 的文件。下方的命令可以运行 类中的 方法
java MethodDescriptorParser
该命令的输出如下
Usage: java MethodDescriptorParser [methodDescriptor ...]
说明我们的使用方法不对(至少应该提供一个 作为参数)。
为了验证各种情况,我使用了很多 ⬇️ (这里的 都来自 [Java] 如何理解 class 文件中方法的 descriptor? 一文)
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'
这个命令的运行结果如下 ⬇️
==================================================
Parse result for method descriptor: ()I
Parameter types: (EMPTY)
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
可以整理成如下的表格
| 入参的类型信息 | 返回值的类型信息 | |
|---|---|---|
()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 |
解析结果符合预期
参考资料
其他
版权
的核心逻辑是我自己写的(在 IntelliJ IDEA (Community Edition) 中写的)。写代码时,我参考了 [Java] 自己写程序,来解析字段的 descriptor 一文中的代码。所以整体而言,没有版权问题,读者朋友可以随意使用 (在遵守法律的前提下),不用告知我。
如何绘制“简要的类图”
借助 trae 和 IntelliJ IDEA (Community Edition) 中的 PlantUML 的插件,我画了“简要的类图”,完整的代码如下
@startuml
title 简要的类图
caption \n\n
' caption 的内容用于避免掘金平台生成的水印遮盖图中的文字
class MethodDescriptorParser {
- {static} final char LEFT_PARENTHESIS
- {static} final char RIGHT_PARENTHESIS
- {static} final char CLASS_TYPE_POSTFIX
- {static} final char ARRAY_TYPE_PREFIX
- {static} final char CLASS_TYPE_PREFIX
- {static} final Map<String, String> baseTypes
- int currIndex
+ MethodDescriptorParser$ParseResult parse(String descriptor)
- List<String> parseParameterDescriptors(String descriptor)
- String parseParameterDescriptor(String descriptor)
- String parseFieldType(String: descriptor)
- String parseReturnDescriptor(String: descriptor)
- void printUsage()
+ {static} void main(String[] args)
}
@enduml