使用varargs参数调用重载方法时出现Nashorn错误
问题内容:
假定以下API:
package nashorn.test;
public class API {
public static void test(String string) {
throw new RuntimeException("Don't call this");
}
public static void test(Integer... args) {
System.out.println("OK");
}
}
以下Nashorn JavaScript代码段将失败:
var API = Java.type("nashorn.test.API");
API.test(1);
第一个方法将被调用,而不是第二个方法。这是Nashorn引擎中的错误吗?
作为记录,此问题以前是在jOOQ用户组上报告的,该方法组中大量使用方法重载和varargs,并且此问题可能会造成很多麻烦。
关于拳击
可能有人怀疑这可能与拳击有关。没有。我这样做时也会出现问题
public class API {
public static void test(String string) {
throw new RuntimeException("Don't call this");
}
public static void test(Integer... args) {
System.out.println("OK");
}
public static void test(MyType... args) {
System.out.println("OK");
}
}
和:
public class MyType {
}
然后:
var API = Java.type("nashorn.test.API");
var MyType = Java.type("nashorn.test.MyType");
API.test(new MyType());
问题答案:
作为为Nashorn编写过载解决机制的人,我总是着迷于人们遇到的极端情况。无论好坏,这都是最终被调用的方式:
Nashorn的重载方法解析尽可能地模仿Java语言规范(JLS),但也允许特定于JavaScript的转换。JLS说,当选择一种方法来调用重载名称时,
只有 在没有适用的固定arity方法时, 才
可以考虑使用可变arity方法进行调用。通常,从Java调用test(String)
不适用于使用调用int
,因此test(Integer...)
将调用该方法。但是,由于JavaScript实际上允许数字到字符串的隐式转换,因此它是适用的,并且在任何可变arity方法之前都要考虑。因此观察到的行为。Arity胜过不转换。如果您添加了test(int)
方法,它是在String方法之前调用的,因为它是固定不变的,并且比String方法更具体。
您可能会争辩说,我们应该更改用于选择方法的算法。自从甚至在Nashorn项目之前(甚至在我独立开发Dynalink时),就已经对此进行了很多思考。当前代码(Nashorn实际在其上构建的Dynalink库中体现)遵循JLS,并且在没有特定语言的类型转换的情况下,将选择与Java相同的方法。但是,一旦您开始放松类型系统,事情就会开始发生细微变化,而放松程度越多,它们的变化就越大(JavaScript放松
很多 ),并且对选择算法的任何更改都会带来一些其他变化恐怕其他人会遇到奇怪的行为……它只是随随便便的类型系统附带的。例如:
- 如果我们允许将varargs与fixargs一起考虑,则需要在不同的arity方法之间发明“比”更具体的关系,这在JLS中不存在,因此与之不兼容,并且会导致varargs有时会在JLS规定fixargs调用时调用。
- 如果我们不允许JS允许的转换(因此强制
test(String)
将其视为不适用于int
参数),则某些JS开发人员会因需要扭曲其程序以调用String方法而感到困惑(例如test(String(x))
,确保x
使用字符串等)。
如您所见,无论我们做什么,都会遭受其他损失;重载方法的选择在Java和JS类型系统之间处于紧要关头,并且即使逻辑上的微小变化也非常敏感。
最后,当您在重载中手动选择时,只要在参数位置对包名称的潜在方法签名没有歧义,也可以坚持使用不合格的类型名称,即
API["test(Integer[])"](1);
应该也可以工作,不需要java.lang.
前缀。除非可以重新编写API,否则这可能会稍微减轻语法噪音。
HTH,阿提拉。