Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
306 views
in Technique[技术] by (71.8m points)

java - Nashorn bug when calling overloaded method with varargs parameter

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

The following Nashorn JavaScript snippet will fail:

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

The first method will be called instead of the second. Is this a bug in the Nashorn engine?

For the record, this issue was previously reported on the jOOQ User Group, where method overloading and varargs are used heavily, and where this issue may cause a lot of trouble.

About boxing

There might be a suspicion that this could have to do with boxing. It doesn't. The problem also appears when I do

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

And:

public class MyType {
}

And then:

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

API.test(new MyType());
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As the guy who wrote the overload resolution mechanism for Nashorn, I'm always fascinated with corner cases that people run into. For better or worse, here's how this ends up being invoked:

Nashorn's overload method resolution mimics Java Language Specification (JLS) as much as possible, but allows for JavaScript-specific conversions too. 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. Normally, when invoking from Java test(String) would not be an applicable to an invocation with an int, so the test(Integer...) method would get invoked. However, since JavaScript actually allows number-to-string implicit conversion, it is applicable, and considered before any variable arity methods. Hence the observed behavior. Arity trumps non-conversion. If you added a test(int) method, it'd be invoked before the String method, as it's fixed arity and more specific than the String one.

You could argue that we should alter the algorithm for choosing the method. A lot of thought has been given to this since even before the Nashorn project (even back when I was developing Dynalink independently). Current code (as embodied in the Dynalink library, which Nashorn actually builds upon) follows JLS to the letter and in absence of language-specific type conversions will choose the same methods as Java would. However, as soon as you start relaxing your type system, things start to subtly change, and the more you relax it, the more they'll change (and JavaScript relaxes a lot), and any change to the choice algorithm will have some other weird behavior that someone else will run into… it just comes with the relaxed type system, I'm afraid. For example:

  • If we allowed varargs to be considered together with fixargs, we'd need to invent a "more specific than" relation among differing arity methods, something that doesn't exist in JLS and thus isn't compatible with it, and would cause varargs to sometimes be invoked when otherwise JLS would prescribe fixargs invocation.
  • If we disallowed JS-allowed conversions (thus forcing test(String) to not be considered applicable to an int parameter), some JS developers would feel encumbered by needing to contort their program into invoking the String method (e.g. doing test(String(x)) to ensure x is a string, etc.

As you can see, no matter what we do, something else would suffer; overloaded method selection is in a tight spot between Java and JS type systems and very sensitive to even small changes in the logic.

Finally, when you manually select among overloads, you can also stick to unqualified type names, as long as there's no ambiguity in potential methods signatures for the package name in the argument position, that is

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

should work too, no need for the java.lang. prefix. That might ease the syntactic noise a bit, unless you can rework the API.

HTH, Attila.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...