背景
在 Java 如何理解 class 文件中方法的 descriptor? 一文中,我们已经了解了 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
我们能否通过解析这个 descriptor 来得到原始的类型信息呢?即类似下表这样的效果 ⬇️
| descriptor | 入参的类型信息 | 返回值的类型信息 |
|---|---|---|
()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 |
先明确一个问题,因为方法的 descriptor 中不包含泛型中的类型信息(例子:如果返回值的类型是 java.util.List<Integer> ,那么其中的 Integer 不会保存在方法的 descriptor 里),所以我们无法通过方法的 descriptor 找到泛型中的类型信息(但是可以找到 java.util.List 这个类型自身)。根据方法的 descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中方法的 descriptor? 一文),看起来可以从方法的 descriptor 中找到方法的完整类型信息(即各个入参的类型信息以及返回值的类型信息)。我们来实战一下。
正文
在 Java 自己写程序,来解析字段的 descriptor 一文中,我写了一个 java 程序,它可以解析字段的 descriptor。在此基础上,我们根据方法的 descriptor 的构造规则(细节可以参考 Java 如何理解 class 文件中方法的 descriptor? 一文),可以写出对应的 Parser,写程序的细节就不赘述了,代码应该还算比较直白。具体的代码如下
代码
请将以下代码保存为 MethodDescriptorParser.java
java
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) {
}
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("Parse result for method descriptor: " + arg);
ParseResult parseResult = parser.parse(arg);
System.out.println("Parameter types: " + String.join(", ", parseResult.parameterTypes));
System.out.println("Return type: " + parseResult.returnType);
}
}
}
编译和运行
使用如下的命令可以编译 MethodDescriptorParser.java
bash
javac MethodDescriptorParser.java
编译之后,当前目录会多出一个名为 MethodDescriptorParser.class 的文件。下方的命令可以运行 MethodDescriptorParser 类中的 main 方法
bash
java MethodDescriptorParser
该命令的输出如下
text
Usage: java MethodDescriptorParser [methodDescriptor ...]
说明我们的使用方法不对(至少应该提供一个 descriptor 作为参数)。
为了验证各种情况,我使用了很多 descriptor ⬇️ (这里的 descriptor 都来自 Java 如何理解 class 文件中方法的 descriptor? 一文)
bash
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'
这个命令的运行结果如下 ⬇️
text
Parse result for method descriptor: ()I
Parameter types:
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
可以整理成如下的表格
| descriptor | 入参的类型信息 | 返回值的类型信息 |
|---|---|---|
()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 |
解析结果符合预期