21

There seems to be a bug in the Java varargs implementation. Java can't distinguish the appropriate type when a method is overloaded with different types of vararg parameters.

It gives me an error The method ... is ambiguous for the type ...

Consider the following code:

public class Test
{
    public static void main(String[] args) throws Throwable
    {
        doit(new int[]{1, 2}); // <- no problem
        doit(new double[]{1.2, 2.2}); // <- no problem
        doit(1.2f, 2.2f); // <- no problem
        doit(1.2d, 2.2d); // <- no problem
        doit(1, 2); // <- The method doit(double[]) is ambiguous for the type Test
    }

    public static void doit(double... ds)
    {
        System.out.println("doubles");
    }

    public static void doit(int... is)
    {
        System.out.println("ints");
    }
}

the docs say: "Generally speaking, you should not overload a varargs method, or it will be difficult for programmers to figure out which overloading gets called."

however they don't mention this error, and it's not the programmers that are finding it difficult, it's the compiler.

thoughts?

EDIT - Compiler: Sun jdk 1.6.0 u18

pstanton
  • 35,033
  • 24
  • 126
  • 168

3 Answers3

13

The problem is that it is ambiguous.

doIt(1, 2);

could be a call to doIt(int ...), or doIt(double ...). In the latter case, the integer literals will be promoted to double values.

I'm pretty sure that the Java spec says that this is an ambiguous construct, and the compiler is just following the rules laid down by the spec. (I'd have to research this further to be sure.)

EDIT - the relevant part of the JLS is "15.12.2.5 Choosing the Most Specific Method", but it is making my head hurt.

I think that the reasoning would be that void doIt(int[]) is not more specific (or vice versa) than void doIt(double[]) because int[] is not a subtype of double[] (and vice versa). Since the two overloads are equally specific, the call is ambiguous.

By contrast, void doItAgain(int) is more specific than void doItAgain(double) because int is a subtype of double according the the JLS. Hence, a call to doItAgain(42) is not ambiguous.

EDIT 2 - @finnw is right, it is a bug. Consider this part of 15.12.2.5 (edited to remove non-applicable cases):

One variable arity member method named m is more specific than another variable arity member method of the same name if:

One member method has n parameters and the other has k parameters, where n ≥ k. The types of the parameters of the first member method are T1, . . . , Tn-1 , Tn[], the types of the parameters of the other method are U1, . . . , Uk-1, Uk[]. Let Si = Ui, 1<=i<=k. Then:

  • for all j from 1 to k-1, Tj <: Sj, and,
  • for all j from k to n, Tj <: Sk

Apply this to the case where n = k = 1, and we see that doIt(int[]) is more specific than doIt(double[]).


In fact, there is a bug report for this and Sun acknowledges that it is indeed a bug, though they have prioritized it as "very low". The bug is now marked as Fixed in Java 7 (b123).

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 3
    by that reasoning, doIt(int) and doIt(double) should also be ambiguous (without the varargs). – Thilo Mar 26 '10 at 06:03
  • 2
    It is the interaction with the varargs that makes this ambiguous, IIRC. You actually have two levels of promotion going on, 1) promotion of a sequence of args to an array, and 2) promotion of integral literals to doubles. – Stephen C Mar 26 '10 at 06:04
  • 2
    I think this is a bug (i.e. the compiler behaviour is not consistent with the JLS.) If I understand that section of the JLS correctly then `doIt(int...)` should be strictly more specific than `doIt(double...)` because `int` is a proper subtype of `double`. It is true that `int[]` is not a subtype of `double[]`, but that is not one of the requirements so it should not affect the overload resolution. – finnw Mar 26 '10 at 11:58
7

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.

Thilo
  • 257,207
  • 101
  • 511
  • 656
4

Interesting. Fortunately, there are a couple different ways to avoid this problem:

You can use the wrapper types instead in the method signatures:

   public static void doit(Double... ds) {
       for(Double currD : ds) {
          System.out.println(currD);
       }
    }

    public static void doit(Integer... is) {
       for(Integer currI : is) {
          System.out.println(currI);
       }
    }

Or, you can use generics:

   public static <T> void doit(T... ts) {
      for(T currT : ts) {
         System.out.println(currT);
      }
   }
Rob Heiser
  • 2,792
  • 1
  • 21
  • 28
  • 2
    You cannot use generics, because then you have only one method. Presumably, the code does something else for doubles than it does for ints. – Thilo Mar 26 '10 at 06:51
  • Good point. I'll stick with using wrapper types in the signature then ::) – Rob Heiser Mar 26 '10 at 07:16
  • 2
    Interesting hack. It works because auto-boxing and promotion are never both applied to the same argument, so by forcing auto-boxing you prevent int-to-double promotion and only one method signature matches. This will *not* work for example #3 (with the `float` values.) – finnw Mar 26 '10 at 11:45