18

Why there is no compile error in this code:

public class OverloadingVarArgs
{
    public void fun1(int... b)
    {
        System.out.println("int");
    }
    public void fun1(long... a)
    {
        System.out.println("long");
    }
    public static void main(String[] args)
    {
        OverloadingVarArgs obj = new OverloadingVarArgs();
        obj.fun1();
    }

}

But this code gives compile error!

public class OverloadingVarArgs
{
    public void fun1(int... b)
    {
        System.out.println("int");
    }
    public void fun1(boolean... a)
    {
        System.out.println("boolean");
    }
    public static void main(String[] args)
    {
        OverloadingVarArgs obj = new OverloadingVarArgs();
        obj.fun1();
    }
}

I believe there should be compile error in both the case, but this is not so.

user3768478
  • 191
  • 5
  • Please post the compile error in full. – Boris the Spider Dec 15 '14 at 11:52
  • 1
    The error for the second one he is getting is: `error: reference to fun1 is ambiguous, both method fun1(int...) in Main and method fun1(boolean...) in Main match obj.fun1(); ^ 1 error` – Sid Dec 15 '14 at 11:57
  • @Sid Then both code examples would have to result in a compiler error since both times the methods are ambiguous. – mezzodrinker Dec 15 '14 at 11:58
  • In the first one, I believe the concept of automatic broader casting comes into picture, as int and long are essentially numbers and could be casted into each other. Boolean, on the other hand, seems to be an entirely unrelated primitive type. Remember, varargs means 0 or more occurrences. – Sid Dec 15 '14 at 12:01
  • in Eclipse Indigo, I am getting compiler error in both the cases, for being ambiguous, because both int[] and boolean[] will be referred by not passing any arguement. JRE1.7 – saikumarm Dec 15 '14 at 12:02
  • @saikumar, the first one actually worked for me with output 'int' – Sid Dec 15 '14 at 12:03
  • @saikumar On the command line, with `javac` from JDK 8, I get the errors as described. – Duncan Jones Dec 15 '14 at 12:03
  • You can use `public void fun1(Object... o)` and check their instance with `instanceof` – Eun Dec 15 '14 at 12:06
  • @Eun That would not be a nice solution. I'd much rather use generics if we were going to use `Object` type. – christopher Dec 15 '14 at 12:09

3 Answers3

9

The rules for selecting the correct overloaded method are as follows:

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

Have a look at that last rule. You can not combine widening or boxing with variable length arguments. That means that the types can not be manipulated in any way and you have to perform the comparison as is. int and long can be compared, no problem and the compiler can deduce that int is the smaller of the two. As per the first rule, it will go for the smallest method argument possible, hence it has worked out the correct (and only) route to a method.

However, when you get to boolean and int, there exists no comparison method between the two because of Java's strong typing. With no knowledge of which type is smallest, the compiler has absolutely no clue which method you mean.

More Visual Example

Let's take it step by step from the perspective of the compiler. First, with int and long.

int and long

Step 1 - Checking if the parameters match any arguments and if so, which one it matches exactly

Well, varargs means that you can pass 0 to many arguments. In this case, you've elected to pass 0 arguments, hence your call matches both the int type and the long type.

Step 2 - Attempt to autobox or widen. This should help it work out which one to go for

You're using varargs, so the compiler knows it can't do this, as per the final rule.

Step 3 - Attempt to work out which type is smallest

The compiler is able to compare the type int with the type long. From this, it works out that the int is the smallest type.

Step 4 - Make the call

With the knowledge that int is the smallest type, it then passes the value to the method for execution.

Okay, and now let's do the same thing with boolean and int.

boolean and int

Step 1 - Checking if the parameters match any arguments and if so, which one it matches exactly

Same story. You've passed nothing so match both arguments.

Step 2 - Attempt to autobox or widen. This should help it work out which one to go for

As above, you're not permitted to do this because you used varargs.

Step 3 - Attempt to work out which type is smallest

This is the crucial difference. Here, the types are not comparable. This means that the compiler doesn't know which method you want to call by your parameters or by the smallest type. Ergo, it has been unable to work out the correct route.

Step 4 - Make the call

Without the knowledge of which method to call, it can not continue execution and throws the appropriate exception.

christopher
  • 26,815
  • 5
  • 55
  • 89
5

In your second example, the compiler is unable to determine the most specific method to invoke.

The gory details are explained in the language spec, but essentially if two variable-arity (var-arg) methods are being compared, then if Method A could accept the arguments passed to Method B, but not the other way around, then Method B is most specific.

In your first example, the rules of primitive sub-typing are applied, which are:

double >1 float

float >1 long

long >1 int

int >1 char

int >1 short

short >1 byte

(Where >1 means 'direct supertype of')

Here we can see that an int is more specific than a long, so your fun1(int... b) method is chosen.

In the second example, the compiler is choosing between an int and boolean. There is no sub-type relationship between those primiritve types, therefore there is no most specific method and "the method invocation is ambiguous, and a compile-time error occurs." (last line in 15.12.2.5).

Community
  • 1
  • 1
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
4

When you have int and long (comparable types), the smallest will be used by default, which is int (since you're not passing an argument) - I think it's because int can be widen to long but long cannot (unless you explicitly cast it), the compiler will select the lowest precision type.

But when you have boolean and int, the comparison cannot be done and you'll get

The method fun1(int[]) is ambiguous for the type OverloadingVarArgs
Maroun
  • 94,125
  • 30
  • 188
  • 241
  • So you're saying because the types are comparable, the compiler can resolve which method is being called by selecting the type with the lowest precision? As opposed to the incompatibility between `boolean` and `int`? – christopher Dec 15 '14 at 12:05
  • 1
    I'm inclined to agree. [This IDEOne](http://ideone.com/N3AkQ4) shows the same effect between `float` and `int`. +1. Excellent catch, as usual! – christopher Dec 15 '14 at 12:08