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

Nashorn bug when calling overloaded method with varargs parameter

本文关键字:Nashorn 错误 方法 varargs 参数 调用 重载 使用      更新时间:2023-09-26

假设以下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的时候),人们就已经对此进行了大量思考。当前代码(具体体现在Dynalink库中,Nashorn实际上是在该库的基础上构建的)完全遵循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");
    }
}

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

这是一个不明确的情况。第二种情况是寻找一个整数数组或一个以上的整数来区分第一种情况。您可以使用方法选择来告诉Nashorn您的意思是哪种情况。

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