我最近给我fork的项目QuickTheories增加了一个接口:

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

这让非常好奇一个方法能够有多少个类型参数呢?据我所知,Java的语言规范并没有提到这个问题。1

关于在实现上这个阈值的定义,我有两个猜测:

  1. 编译器会强制一个可预测的阈值,例如255或者65535。
  2. 由于实现细节的原因,编译器的异常处理会施加意想不到的限制。

我不想通过我薄弱的C++技能来测试源代码,所以我决定直接来测试编译器2。我写了一个Python脚本,通过二分法找到一个会触发错误的最小值。完整的代码请见连接Github Repo

最直接的办法就是生成方法。幸运的是,我们不必使用任何已有的类型参数,只需要按照<A,B,C..>的形式来生成:

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("}")

运行这个二分法的代码会有如下输出:

>>> error: UTF8 representation for string "<A1:Ljava/lang/Objec..." is too long for the constant pool
>>> largest type: 2776

这个错误让人有点费解,但是从事后来看还是可以理解的。编译器生成的类文件包含多个字符串,包括每个方法的方法签名。这些字符串保存在常量池内,而常量池的内容有最大65535字节数的限制,这个是JVM的所定义的。

所以,我之前的猜测都不是完全的正确。类型参数的最大个数是一个意料之外的值,而不是一个确定值。但是,编译器的实现本身并不是导致错误的原因3。相反,是JVM类文件的格式要求限制了类型参数可使用的数量。其实JVM对泛型本身一无所知。

这同时也表示类型参数的最大个数取决于你写的方法代码4。我尝试用另外一种类型参数的编码方案(先前链接文中的write_type_compact),使用全部合法的ASCII字符。这个实现是有点繁琐的,因为字符0-9是合法的,但不能作为标识符的首字母,并且Java关键字也不能作为类型参数。我仅仅将ifdo替换为等长的UTF-8字符。采用这种更紧凑的编码方案让类型参数的个数从2776提升到了3123。

还是有一些不太方便的地方,例如_A是一个合法的Java标识符,但是_不是。我的编码在不使用_作为首字幕的情况下,最高生成了3392个2字节的类型参数。所以我觉得不用考虑_作为首字母的情况了。

另外一个技巧

通过反编译类文件,我观察到65536个字符中大部分都不是我生成的类型参数,而是重复的字符串Ljava/lang/Object;。这是因为类型参数没有包含额外的信息,所以类文件将其视为Object的继承,并将它们编入方法签名内。我通过修改我的生成器来优化这个问题。

循环的关键代码修改为:

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

除开一个实例之外,所有的类型参数都从继承java/lang/Object改为继承A。这个修改将类型参数的数量提升到9851个。

类型参数的数量提升了非常多,而我所使用的编码方法还可以继续改进。例如使用非ASCII unicode标识符,不过我已经比较满意现在的效果了。

这些都不重要

在实际情况中是不太可能达到上述数量限制的。代码生成时可能会达到语言或者编译器的某些极限,就算罕见的遇到了生成上百个类型参数的情况,那距离几千个的限制仍然还相距很远。

尽管如此,如果我是规则的制定者,我将不允许任何类或者方法使用超过255个类型参数的情况。即使只影响了百万分之一的程序,有明确的限制会更好。

  1. §4.4, §8.1.2, §9.1.2, §8.4.4, §8.8.4 这些章节都和方法或者类的类型参数有关,但是都没有指明允许有多少个类型参数。
  2. 当我写这段话时,我想起了Hotspot是C++写的,javac是Java写的。就算这样我依然会选择做代码实验,而不是阅读代码。阅读别人代码是种煎熬
  3. 逗号之后的空格不会影响,因为编译器会规范化它的输出。
  4. 这也表示与我使用哪个JVM无关。为了完整性,我在Fedora 29上使用了1.8.0_191-b13版本的OpenJdk。

本文作者:justinblank, 翻译:1 Way

原文链接:https://justinblank.com/experiments/howmanytypeparameterscanajavamethodhave.html

译文首发:http://blog.didispace.com/howmanytypeparameterscanajavamethodhave/

本文有spring4all技术翻译组完成,更多国外前沿知识和干货好文,欢迎关注公众号:后端面试那些事儿。

一个Java方法能使用多少个参数?的更多相关文章

  1. java方法中增加不固定参数

    JDK1.5以上支持 一.定义方法 有时方法的参数个数不固定,可以使用...来省略个数,使用时直接遍历即可,例如下面的方法 public class hi { public void print(St ...

  2. idea java方法中 传多个参数对象 的复制粘贴快速处理方法

    比如像这种的传多个参数对象,我是直接复制过来,然后把第一个字母改成大写,然后后面的实例对象敲一个第一个字符的小写,回车就直接出来了 在写调用参数的地方,ctrl+p 调出提示,然后按下提示里的实例的第 ...

  3. java web项目初始化启动一个java方法

    项目中需要一些初始化数据,或者加载中断的任务. 首先在web.xml中配置信息,配置在<web-app>中: <servlet> <servlet-name>Ini ...

  4. 一个Java方法覆盖的小问题

    class SuperClass{ public SuperClass() { System.out.println("superclass "); show(); } publi ...

  5. 辨析Java方法参数中的值传递和引用传递

    小方法大门道 小瓜瓜作为一个Java初学者,今天跟我说她想通过一个Java方法,将外部变量通过参数传递到方法中去,进行逻辑处理,方法执行完毕之后,再对修改过的变量进行判断处理,代码如下所示. publ ...

  6. C#调用Java方法(详细实例)

    C#可以直接引用C++的DLL和转换JAVA写好的程序.最近由于工作原因接触这方面比较多,根据实际需求,我们通过一个具体例子把一个JAVA方法转换成可以由C#直接调用的DLL C#调用c++ C#调用 ...

  7. Android C代码回调java方法

    本文将讲述下列三种C代码回调java方法 1.c代码回调java空方法 2.c代码回调java int类型参数方法 3.c代码回调javaString类型参数方法 方法都差不多,先看c代码回调java ...

  8. C#调用Java方法

    C#调用Java方法(详细实例) 阅读目录 C#调用c++ C#调用JAVA方法 C#可以直接引用C++的DLL和转换JAVA写好的程序.最近由于工作原因接触这方面比较多,根据实际需求,我们通过一个具 ...

  9. cocos2d-x中使用JNI的调用JAVA方法

    用cocos2d-x公布Android项目时.都应该知道要用JAVA与C/C++进行交互时会涉及到JNI的操作(Java Native Interface).JNI是JAVA的一个通用接口.旨在本地化 ...

随机推荐

  1. vs遇到的字符串问题

    原以为自己的字符串已经理解不错了, 今天又被vs搞了. 情景就不说了, 直接说结果: 有两种情况 1 当文件是存储为gbk或者utf-8的时候, 中文字符存储永远是gbk的值. ‘按’字的gbk编码 ...

  2. JS promise对象

    Promise 承诺 一:promise概念 在js中,promise是一个对象,是专门用来处理异步操作 / 回调地狱的,那么什么是回调地狱?设计到同步和异步任务的知识,在js中,因为js是单线程,无 ...

  3. uni-app 使用个推推送系统消息

    原文可查看此处 ,搜索 uni-app 使用个推推送系统消息 https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=Mzg3NTA ...

  4. 0ctf_2016 _Web_unserialize

    0x01 拿到题目第一件事是进行目录扫描,看看都有哪些目录,结果如下: 不少,首先有源码,我们直接下载下来,因为有源码去分析比什么都没有更容易分析出漏洞所在. 通过这个知道,它一共有这么几个页面,首页 ...

  5. TP5中的缓存使用

    Thinkphp 5.0采用了 think\Cache 类来提供缓存支持 缓存支持采用驱动方式,所以缓存在使用之前,需要进行连接操作,也就是缓存初始化操作. 支持的缓存类型包括file.memcach ...

  6. Scala 基础(九):Scala 函数式编程(一)基础(一)概念、定义、调用机制

    1 概念的说明 1)在scala中,方法和函数几乎可以等同(比如他们的定义.使用.运行机制都一样的),只是函数的使用方式更加的灵活多样. 2)函数式编程是从编程方式(范式)的角度来谈的,可以这样理解: ...

  7. python面向对象07/异常处理

    python面向对象07/异常处理 目录 python面向对象07/异常处理 1. 异常错误分类 2. 什么是异常? 3. 异常处理 4. 为什么要有异常处理 5. 异常处理的两种方式 1.if判断 ...

  8. 谈谈JVM(基础模型)

    一,基本概念      JVM是可运行Java代码的假想计算机 ,包括一套字节码指令集.一组寄存器.一个栈. 一个垃圾回收,堆 和 一个存储方法域.     JVM 是运行在操作系统之上的,它与硬件没 ...

  9. 安卓移动端line-height垂直居中出现偏移的解决方法

    目前移动端在项目使用的rem,安卓手机上line-height属性,让它的值等于height,结果发现是不居中的. 出现此问题的原因是Android在排版计算的时候参考了primyfont字体的相关属 ...

  10. 牛客练习赛 66C公因子 题解

    原题 原题 思路 考场想复杂了,搞到自闭-- 实际上,因为差值不变,我们可以先差分,求\(\gcd\),便得到答案(考场时想多了,想到了负数.正数各种复杂的处理,但是不需要),最后处理一下即可 代码 ...