Bug with Varargs and Overloading

bug with varargs and overloading?

There is a discussion about this over at the Sun Forums.

No real resolution there, just resignation.

Varargs (and auto-boxing, which also leads to hard-to-follow behaviour, especially in combination with varargs) have been bolted on later in Java's life, and this is one area where it shows. So it is more a bug in the spec, than in the compiler.

At least, it makes for good(?) SCJP trick questions.

Nashorn bug when calling overloaded method with varargs parameter

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.

Behavior of method overloading with varargs

It's a typo

void varagrs(long... l){
^^

That's why it's nice to have an IDE with spell checking (e.g. IntelliJ)

After fixing the typo, the compiler chooses (int...) over (long...) because int is a subtype of long (4.10.1), so the 1st method is more specific (15.12.2.5). Note though int[] is not a subtype of long[] (4.10.3).

Ambiguous Overload in Scala 3 with Varargs

This behaviour is mentioned in this PR, in particular in this comment. I don't know if that will be "fixed" or if it's exactly as intended.

Note that the following is not ambiguous:

def foo(s: String)
def foo(a: String, other: String*)
foo("fine!")

With def foo(s: Any), you can explicitly select one overload or the other like this:

foo("fine": Any) // calls foo(Any)
foo("fine", Nil: _*) // calls foo(String, String*) with an empty vararg

PS: I wouldn't say that overloading is "strongly discouraged", but we must be careful and admit that some cases are ambiguous, to the compiler and to humans. It's not obvious to me which definition foo("") should refer to, so it's not completely crazy to be more explicit in that case.

Overloading function using varargs

This is a high-level informal summary of what is going on.

Firstly varargs syntax is really just syntactic sugaring for passing an array. So method(7) is actually going to pass an array of ... something.

But an array of what? There are two options here corresponding to the two overloads of the method; i.e an int[] or a Integer[].

If there are two or more overloads that could work (i.e. right method names, right numbers of arguments, convertible values) then the resolution process will chose the overload that is an exact match over a match that requires conversions, and complain if the only candidates require conversions. (This is a drastic simplification of the rules ... see the JLS section 15.12 for the complete story ... and be prepared for a long / difficult read!)

So what is happening in your first example is that it is trying to decide between two methods that both require conversions; i.e. int to int[] versus int to Integer[]. Basically it cannot decide which alternative to use. Hence a compilation error that says that the call is ambiguous.

If you change the varargs call to a call passing an explicit Integer[] or int[], you now get an exact match to one of the two overloads ... and the rules above say that this is not ambiguous.


I understand it as: 7 is primitive so it should be converted to array - int[].

The problem is that 7 can also be converted to an Integer[] ... by auto-boxing the int first.

Varargs in method overloading in Java

You can either Widen or Box but you cannot do both, unless you are boxing and widening to Object (An int to Integer(Boxing) and then Integer to Object(Widening) is legal, since every class is a subclass of Object, so it is possible for Integer to be passed to Object parameter)

Similarly an int to Number is also legal (int -> Integer -> Number)
Since Number is the super class of Integer it is possible.

Let's see this in your example:

public static void test(Integer...i)

public static void test(Float...f)

There are some rules that are followed when selecting which overloaded method to select, when Boxing, Widening, and Var-args are combined:


  1. Primitive widening uses the smallest method argument possible
  2. Wrapper type cannot be widened to another Wrapper type
  3. You can Box from int to Integer and widen to Object but no to Long
  4. Widening beats Boxing, Boxing beats Var-args.
  5. You can Box and then Widen (An int can become Object via Integer)
  6. You cannot Widen and then Box (An int cannot become Long)
  7. You cannot combine var-args, with either widening or boxing

So, based on the above given rules:

When you pass two integers to above functions,

  • according to rule 3, it will have to be first Widened and then
    Boxed to fit into a Long, which is illegal according to rule 5 (You cannot Widen and then Box).
  • So, it is Boxed to store in Integer var-args.

But in first case, where you have methods with var-args of primitive types:

public static void test(int...i)
public static void test(float...f)

Then test(1, 2) can invoke both the methods (Since neither of them is more suitable for rule 1 to apply):

  • In first case it will be var-args
  • In second case, it will be Widening and then Var-args (which is allowed)

Now, when you have methods with exactly one int and one float:

public static void test(int i)
public static void test(float f)

Then on invoking using test(1), rule 1 is followed, and smallest possible widening (i.e. the int where no widening is needed at all) is chosen. So 1st method will be invoked.

For more information, you can refer to JLS - Method Invocation Conversion

Method overloading with variable arguments (varargs)

This behaviour is due to the fact that int is more specific than double while there is no such comparison between int and boolean.

As specified in the JLS section 15.12.2.5 (emphasis mine):

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

  • ...
  • m2 is not generic, and m1 and m2 are applicable by variable arity invocation, and where the first k variable arity parameter types of m1 are S1, ..., Sk and the first k variable arity parameter types of m2 are T1, ..., Tk, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ k). Additionally, if m2 has k+1 parameters, then the k+1'th variable arity parameter type of m1 is a subtype of the k+1'th variable arity parameter type of m2.

What more specific actually means is later defined with subtyping:

A type S is more specific than a type T for any expression if S <: T.

This means that S is more specific than T is S is a subtype of T. For primitive types, this comes down to the following properties:

  • double > float
  • float > long
  • long > int
  • int > char
  • int > short
  • short > byte

Notice that boolean is not there.

As such,

public static void main(String[] args) {
movie();
}

static void movie(int... x) { }
static void movie(short... x) { }
static void movie(double... x) { }
static void movie(byte... x) { }

compiles and movie(byte... x) will be called because it is the most specific.

However,

public static void main(String[] args) {
movie();
}

static void movie(int... x) { }
static void movie(boolean... x) { }

does not compile because boolean cannot be compared to int.



Related Topics



Leave a reply



Submit