Java 方法最多支持多少个类型参数
最近我为 QuickTheories 新增了一个接口:
```java@FunctionalInterfacepublic interface QuadFunction<A, B, C, D, E> { E apply(A a, B b, C c, D d);}```
这让我想知道一个方法最多支持多少个类型参数。据我所知,Java 语言规范中没有提到这个问题1。
我猜测可能有两个限制:
- 编译器中设置限制,比如255或65535。
- 编译器为意外情况设置限制,比如堆栈溢出或类似不可预测的情况。
我并不想在 C++ 源代码中摸索,所以决定从编译器下手2。我写了一个 Python 脚本,用二分查找确定造成编译器报错的最少参数。完整的脚本可以在 Github 仓库 中找到。
生成方法很简单。幸运的是,只要像 <A, B, C...>
这样声明即可,不需要实际使用:
```pythondef write_type_plain(count): with open('Test.java', 'w') as f: f.write("public class Test {\n") f.write("public <") for i in range(count): if (i > 0): f.write(", ") f.write("A" + str(i + 1)) f.write("> void testMethod() {}") f.write("}")```
执行二分查找结果如下:
```shell>>> error: UTF8 representation for string "<A1:Ljava/lang/Objec..." is too long for the constant pool>>> largest type: 2776```
虽然上面的报错信息有点难以理解,但事后看来是还是可以知道限值大小。编译器生成的 class 文件包含了许多字符串,其中包括类中每个方法的签名。这些字符串存储在常量池中,JVM 规范中常量池最大65535字节。
所以,之前的猜测都不完全正确。类型参数最大数量不是固定值,视具体情况而定。尽管如此,不是编译器本身导致报错3,而是 JVM class 文件格式限制了类型参数的最大个数。尽管 JVM 不处理泛型,但结论是对的。
这意味着,类型参数的最大数量完全取决于如何定义方法4。我尝试了一种新的类型参数编码方式,在脚本文件中使用 write_type_compact,全部使用合法 ASCII 字符(A-Z, a-z, $ 和 _ )。这种实现有点复杂,可以使用 0-9 但不能用作标识符的初始字符,而且不能使用 Java 关键字。通过把 if
、do
替换为长度相同的 UTF-8 字符,参数的最大数量从2776增加到3123。
_A 是合法 Java 标识符,但 _ 不是。这点不是很方便。庆幸的是,即使不使用 _ ,脚本顺利生成了3392个2字节的类型参数,所以我不觉得有必要考虑 _ 作为首字符的情况。
还有一个技巧
反编译 class 文件发现,65536字符中大部分是重复的 Ljava/lang/Object;
字符串,并非我生成的类型参数。由于缺少类型参数定义信息,因此 class 文件会默认它们继承了 Object
对象,方法签名中也包含了类似信息。为此,我修改了生成脚本并解决了这个问题。
循环关键代码:
```pythons = type_var(i)f.write(s)if (s != 'A'): f.write(" extends A")```
除一个实例继承 java/lang/Object
外,所有其他类型参数都继承类型 A。修改后,编译通过的参数最大数量增加到9851个。
目前为止,类型参数最大数量已经提升了很多。当然,还可以在字符编码上继续改进,比如使用非 ASCII Unicode 标识符。
上面这些都不重要
很难想象实际编程中会有人达到这个极限。代码生成有时会到达语言或编译器的极限,但似乎也不太可能用到成百上千个类型参数。
尽管如此,假如我是 Java 国王,我会规定类、方法的类型参数不得超过255个。有明确的个数限制似乎更好,即使可能只影响百万分之一的程序。
- [§4.4][3]、[§8.1.2][4]、[§9.1.2][5]、[§8.4.4][6]、[§8.8.4][7] 都与方法或类的类型参数相关,但没有提到最大允许使用多少个参数。[↩][8]
- 完成本文最后一行,我突然记起来虽然 Hotspot 用的是 C++,但 javac 是用 Java 写的。如果动手之前意识到这一点,我可能还会做实验而不是去阅读源代码。他人代码即地狱。[↩][9]
- 逗号后面的空格无关紧要,因为编译器会对输出进行规范化。[↩][10]
- 这也意味着,使用哪个 JVM 其实并不重要。为了确保完整性,我在 Fedora 29 上使用 OpenJDK 1.8.0191-b13 进行实验。[↩][11]
转载自:https://juejin.cn/post/6873793381542297613