使用varargs参数调用重载方法时的Nashorn错误

时间:2014-09-01 09:54:42

标签: java javascript java-8 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引擎中的一个错误吗?

For the record, this issue was previously reported on the jOOQ User Group,其中大量使用方法重载和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());

3 个答案:

答案 0 :(得分:26)

作为为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,   阿提拉。

答案 1 :(得分:7)

这些是有效的解决方法:

使用数组参数显式调用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");
    }
}

String(或任何其他“类似类型”)替换CharSequence

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

答案 2 :(得分:4)

这是一个模棱两可的情况。第二种情况是它寻找一个整数数组或多个整数来辨别第一种情况。您可以使用方法选择告诉Nashorn您的意思是哪种情况。

API["test(java.lang.Integer[])"](1);