19

假设以下 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 用户组上报告过,其中方法重载和可变参数被大量使用,并且这个问题可能会导致很多麻烦。

关于拳击

可能有人怀疑这可能与拳击有关。它没有。当我这样做时也会出现问题

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());
4

3 回答 3

29

作为为 Nashorn 编写过载解决机制的人,我总是对人们遇到的极端情况很着迷。无论好坏,以下是最终调用它的方式:

Nashorn 的重载方法解析尽可能地模仿 Java 语言规范 (JLS),但也允许特定于 JavaScript 的转换。JLS 表示,在为重载名称选择要调用的方法时,只有在没有适用的固定 arity 方法时才可以考虑调用可变 arity方法。通常,从 Java 调用时test(String)不适用于带有 的调用int,因此test(Integer...)会调用该方法。但是,由于 JavaScript 实际上允许数字到字符串的隐式转换,因此它是适用的,并且在任何可变参数方法之前都要考虑。因此观察到的行为。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,阿提拉。

于 2014-09-01T17:52:14.120 回答
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");
    }
}

删除可变参数:

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

替换StringCharSequence(或任何其他“类似类型”):

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");
    }
}
于 2014-09-01T09:54:41.823 回答
4

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

API["test(java.lang.Integer[])"](1);
于 2014-09-01T11:50:34.280 回答