【问题标题】:Nashorn bug when calling overloaded method with varargs parameter使用可变参数调用重载方法时出现 Nashorn 错误
【发布时间】:2014-10-25 12:38:02
【问题描述】:

假设以下 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 sn-p 将失败:

var API = Java.type("nashorn.test.API");
API.test(1);

第一个方法将被调用而不是第二个。这是 Nashorn 引擎中的错误吗?

For the record, this issue was previously reported on the jOOQ User Group,其中方法重载和可变参数被大量使用,这个问题可能会带来很多麻烦。

关于拳击

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

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 做了正确的事情。
  • @zeroflagL:但我什至不使用字符串调用该方法。不涉及字符串类型!如果这是一个模棱两可的情况,我宁愿抛出异常而不是调用“错误”(即意外)方法
  • 你调用一个带有一个参数的方法,它不是一个数组。 API 有一个带有单个非数组/非可变参数的方法。匹配。来自文档:如果 Java 方法需要 String 或 Boolean 对象,则将使用 JavaScript 规范定义的 ToString 和 ToBoolean 转换允许的所有转换来转换值。虽然我理解你的立场,但对我来说这似乎是一种合理的行为。
  • @zeroflagL: If you mean this documentation 我只想说没有指定可变参数的情况,至少没有在我们期望 JLS 的粒度级别上......但是@987654323 @,我现在可以从 JavaScript 开发人员的角度看到这种行为的意义。

标签: java javascript java-8 nashorn


【解决方案1】:

这些是有效的解决方法:

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

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】:

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

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

    【讨论】:

    • 感谢您的回复!但这完全违背了任何 Java 开发人员的直觉。如果我们假设 some 类型检查,那么Integer... 方法肯定比String 方法更匹配,因为我实际上传递了一个“Integer”。否则,如果调用不明确,我会感激至少一个错误,而不是仅仅选择直观“不太正确”的错误
    • 实际上你传递了一个 int 并期望它被自动装箱成一个 Integer。如果你真的明确地传递一个整数会发生什么?
    • @Keilly:任何类型都会发生这种情况,而不仅仅是“可装箱”的类型。我会更新我的问题
    【解决方案3】:

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

    Nashorn 的重载方法解析尽可能地模仿 Java 语言规范 (JLS),但也允许特定于 JavaScript 的转换。 JLS says that when selecting a method to invoke for an overloaded name, variable arity methods can be considered for invocation only when there is no applicable fixed arity method.通常,从 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 方法(例如执行 @ 987654330@ 确保x 是一个字符串等。

    如您所见,无论我们做什么,都会有其他事情受到影响;重载的方法选择在 Java 和 JS 类型系统之间处于紧要关头,对逻辑中的微小变化都非常敏感。

    最后,当您在重载中手动选择时,您还可以坚持使用非限定类型名称,只要参数位置中包名称的潜在方法签名没有歧义,即

    API["test(Integer[])"](1);
    

    应该也可以,不需要java.lang. 前缀。除非您可以重新编写 API,否则这可能会稍微减轻语法噪音。

    HTH, 阿提拉。

    【讨论】:

    • 非常感谢您的有趣回答!我可以看到这对于大多数 API 消费者来说是一个极端案例。不幸的是,它不适用于jOOQ 用户,因为jOOQ 大量使用重载和可变参数来模仿SQL 作为一种语言。我明白你为什么会这样决定,而且你肯定经历了很多关于替代方案的思考。从 JS 的角度来看,将数字到字符串的转换优先于 varargs 调用可能是有意义的,但是我添加了另一个示例,其中我使用了 MyType 参数,在这种情况下,我真的不明白为什么要进行转换优先考虑。
    • 无论如何,我认为在调用站点将参数显式包装在数组中的解决方法对于用户来说是可以接受的。除此之外,在这种情况下,我们可能可以重新设计 API 以添加更多重载:test(String)test(Integer...)test(Integer)。无论如何,再次感谢这个非常有趣的答案!
    • JS 还允许对象到字符串的隐式转换,因此系统将再次将test(String) 视为匹配arity,并通过调用myTypeInstance.toString() 来应用。 java.lang.String 有点特殊,因为它被用作原始 JavaScript string 类型的表示。
    • 我明白这一点,但从互操作性的角度来看,这很奇怪。想象一下该方法接受了CharSequence 而不是String。那么它会“按预期”工作!因此,如果一个 API 被 Nashorn 客户端使用,那么当 API 发展时这些客户端的回归风险是相当大的,因为 API 设计者肯定不会想到这些相当不寻常的(从 Java 角度来看)警告。
    • 您好 Attila,感谢您的澄清。我还有一个关于 Nashorn 的重载解决方案的极端案例,您可能会感兴趣。这是关于超载的。每当重载字段实例和不带参数的方法时,[] 运算符都会返回方法引用。我已经在下面正确描述了这个问题:stackoverflow.com/questions/9960560/…stackoverflow.com/questions/9960560/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 2020-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多