使用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方法之前被调用,因为它是固定的arity并且比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,阿提拉。
这些是有效的解决方法:
使用数组参数显式调用test(Integer[])
方法:
var API = Java.type("nashorn.test.API"); API.test([1]);
删除过载:
public class AlternativeAPI1 { public static void test(Integer... args) { System.out.println("OK"); } }
删除varargs:
public class AlternativeAPI3 { public static void test(String string) { throw new RuntimeException("Don't call this"); } public static void test(Integer args) { System.out.println("OK"); } }
用CharSequence
(或任何其他“类似类型”)替换String
:
public class AlternativeAPI2 { public static void test(CharSequence string) { throw new RuntimeException("Don't call this"); } public static void test(Integer args) { System.out.println("OK"); } }
这是一个模棱两可的情况。 第二种情况是它寻找一个整数数组或多个整数来辨别第一种情况。 您可以使用方法选择告诉Nashorn您的意思是哪种情况。
API["test(java.lang.Integer[])"](1);