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

Java 使用varargs参数调用重载方法时出现Nashorn错误,java,javascript,java-8,nashorn,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"); } } 以

假设以下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引擎中的错误吗

,其中大量使用方法重载和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");
    }
}
以及:

然后:

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

API.test(new MyType());

这些是有效的解决办法:

使用数组参数显式调用
test(Integer[])
方法: 消除过载: 删除varargs: 将
字符串
替换为
字符序列
(或任何其他“类似类型”):
这是一个模棱两可的局面。第二种情况是寻找一个整数数组或多个整数来与第一种情况区分开来。您可以使用方法选择来告诉Nashorn您指的是哪种情况

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

作为为Nashorn编写过载解决机制的人,我总是对人们遇到的角落案例着迷。不管是好是坏,下面是如何调用它的:

Nashorn的重载方法解析尽可能模仿Java语言规范(JLS),但也允许特定于JavaScript的转换。JLS说,当为重载名称选择要调用的方法时,只有在没有适用的固定arity方法时,才可以考虑调用变量arity方法。通常,从Java调用时,
test(String)
不适用于使用
int
的调用,因此
test(Integer…
方法将被调用。然而,由于JavaScript实际上允许数字到字符串的隐式转换,所以它是适用的,并且在任何变量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

嗯,,
Attila。

谢谢您的回答!但这完全违背了任何Java开发人员的直觉。如果我们假设进行某种类型检查,那么
Integer…
方法肯定比
String
方法更匹配,因为我实际上传递了一个“
Integer
”。否则,如果调用不明确,我希望至少出现一个错误,而不仅仅是选择直觉上“不太正确”的一个。实际上,您正在传递一个int,并希望它自动装箱为整数。如果您确实显式传递一个整数,会发生什么情况?@Keilly:任何类型都会发生这种情况,而不仅仅是“boxable”1。我将更新我的问题。因为varargs是数组的语法糖,我认为Nashorn做的是正确的。@zeroflagL:但我甚至没有用字符串调用该方法。没有涉及字符串类型!如果这是一个不明确的情况,我宁愿抛出一个异常而不是“错误”(即意外的)被调用的方法使用一个参数调用一个方法,该参数不是数组。
API
有一个带有单个非数组/非varargs参数的方法。Match。从文档中可以看出:如果Java方法需要字符串或布尔对象,则将使用t定义的ToString和ToBoolean转换所允许的所有转换来转换值他解释了JavaScript规范。虽然我理解你的立场,但对我来说这似乎是一个合理的行为。@zeroflagL:我只是想说,varargs案例没有被指定,至少没有按照我们从JLS中期望的粒度级别指定……但是,现在,我可以从JavaScript开发人员的角度来看,这种行为是有意义的。谢谢非常感谢你有趣的回答!我可以看出这对于大多数API使用者来说是一个极端情况。不幸的是,它不适合用户,因为jOOQ大量使用重载和varargs来模仿SQL
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");
    }
}
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");
    }
}
API["test(java.lang.Integer[])"](1);
API["test(Integer[])"](1);