您的位置:首页 > 编程语言 > Java开发

Java 方法最多支持多少个类型参数

大圣归来 2021-01-13 20:43 98 查看 https://blog.51cto.com/1508239

最近我为 QuickTheories 新增了一个接口:


```java
@FunctionalInterface
public interface QuadFunction<A, B, C, D, E> {
   E apply(A a, B b, C c, D d);
}
```


这让我想知道一个方法最多支持多少个类型参数。据我所知,Java 语言规范中没有提到这个问题<sup>1</sup>。


我猜测可能有两个限制:


  1. 编译器中设置限制,比如255或65535。

  2. 编译器为意外情况设置限制,比如堆栈溢出或类似不可预测的情况。


我并不想在 C++ 源代码中摸索,所以决定从编译器下手<sup>2</sup>。我写了一个 Python 脚本,用二分查找确定造成编译器报错的最少参数。完整的脚本可以在 [Github 仓库][1] 中找到。


[1]:https://github.com/hyperpape/java-max-type-params


生成方法很简单。幸运的是,只要像 `<A, B, C...>` 这样声明即可,不需要实际使用:


```python
def 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字节][2]。


[2]:https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-4.html#jvms-4.4.7


所以,之前的猜测都不完全正确。类型参数最大数量不是固定值,视具体情况而定。尽管如此,不是编译器本身导致报错<sup>3</sup>,而是 JVM class 文件格式限制了类型参数的最大个数。尽管 JVM 不处理泛型,但结论是对的。


这意味着,类型参数的最大数量完全取决于如何定义方法<sup>4</sup>。我尝试了一种新的类型参数编码方式,在脚本文件中使用 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` 对象,方法签名中也包含了类似信息。为此,我修改了生成脚本并解决了这个问题。


循环关键代码:


```python
s = type_var(i)
f.write(s)
if (s != 'A'):
   f.write(" extends A")
```


除一个实例继承 `java/lang/Object` 外,所有其他类型参数都继承类型 A。修改后,编译通过的参数最大数量增加到9851个。


目前为止,类型参数最大数量已经提升了很多。当然,还可以在字符编码上继续改进,比如使用非 ASCII Unicode 标识符。


上面这些都不重要


很难想象实际编程中会有人达到这个极限。代码生成有时会到达语言或编译器的极限,但似乎也不太可能用到成百上千个类型参数。


尽管如此,假如我是 Java 国王,我会规定类、方法的类型参数不得超过255个。有明确的个数限制似乎更好,即使可能只影响百万分之一的程序。


  1. [§4.4][3]、[§8.1.2][4]、[§9.1.2][5]、[§8.4.4][6]、[§8.8.4][7] 都与方法或类的类型参数相关,但没有提到最大允许使用多少个参数。[↩][8]

  2. 完成本文最后一行,我突然记起来虽然 Hotspot 用的是 C++,但 javac 是用 Java 写的。如果动手之前意识到这一点,我可能还会做实验而不是去阅读源代码。他人代码即地狱。[↩][9]

  3. 逗号后面的空格无关紧要,因为编译器会对输出进行规范化。[↩][10]

  4. 这也意味着,使用哪个 JVM 其实并不重要。为了确保完整性,我在 Fedora 29 上使用 OpenJDK 1.8.0191-b13 进行实验。[↩][11]



标签: