5

Can someone help explain why test 2 & test 3 have issue?

public static void main (String[] args) {
    byte b = 5;
    doCalc(b, b);
}

Test 1: These two methods have no ambiguous issue.

static void doCalc(byte a, byte b) {
    System.out.print("byte, byte");
}
static void doCalc(Byte s1, Byte s2) {
    System.out.print("Byte, Byte");
}

Test 2: These two methods has no Compile-time ambiguous, but it has run-time ambiguous.

static void doCalc(Byte... a) {
    System.out.print("byte 1...");
}
static void doCalc(long... a) {
    System.out.print("byte 2...");
}

Test 3: These two methods has Compile-time ambiguous.

static void doCalc(Byte... a) {
    System.out.print("byte 1...");
}
static void doCalc(byte... a) {
    System.out.print("byte 2...");
}
Abe
  • 310
  • 3
  • 15
  • 1
    Probably because varargs are treated like arrays by the VM. Maybe there is some sort of type erasure happening. This may help: [Method Overloading and Ambiguity in Varargs in Java (GeeksforGeeks)](https://www.geeksforgeeks.org/method-overloading-ambiguity-varargs-java/) and this question: [Ambiguous varargs methods](https://stackoverflow.com/questions/27874380/ambiguous-varargs-methods). There are a ton of Google results for "varargs ambiguous java". – Mr. Polywhirl Jul 19 '23 at 18:00

2 Answers2

2

In the first case, there is no error because the parameter types of these two methods are different, and there is no overloading ambiguity. The compiler can unambiguously determine which method to call based on the type of argument passed during the invocation.

What about the 2nd and 3rd tests, here everything is much more interesting. Varargs automatically wrap primitive data types into their corresponding wrapper classes if the method takes the varargs that correspond to primitive types. (byte... => Byte... when calling a method).

In the 2nd case, if you call doCalc(12, 1) for example, 12 and 1 are treated as int literal, but it can be automatically transfered into a long (int -> long). So the method doCalc(long... a) has an advantage because it can directly accept an argument of type long (cannot convert from int to byte for int -> byte -> Byte, but can from int to long), which is a primitive long.

I got the next output here: byte 2...

But you can call the method as follows:

byte b = 1; 
doCalc(b, b);

In this case, there will be a compile-time error, because it can convert byte -> int -> long and also it can convert byte -> Byte as well.

Also, you can experiment with the methods like that:

static void doCalc(Long... a) {
    System.out.print("byte 2...");
}

Here even if you call doCalc(1, 1), there will be an compile-time error, because it can't wrap int into Long.

What about 3rd case, you have two non-generic methods (Byte... vs Byte...). In order for one of the methods to be chosen, one of them has to be more specific than the other. In your case, each method only has one parameter, and for one of them to be more specific than the other, the type of that one parameter must be a subtype of the other method's parameter.

Since byte is not a subtype of Byte there is no most specific method and the compiler throws a compile-time error.

Check this out. JLS 15.12.2.5 Choosing the Most Specific Method: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5

Also, check these answers:

  1. Method overload ambiguity with Java 8 ternary conditional and unboxed primitives
  2. Why is this method overloading ambiguous?
1

In my test, this code:

static void doCalc(Byte... a) {
    System.out.print("byte 1...");
}
static void doCalc(long... a) {
    System.out.print("byte 2...");
}

public static void main (String[] args) {
    byte b = 5;
    doCalc(b, b);
}

produces compilation error:

error: reference to doCalc is ambiguous doCalc(b, b); ^ both method doCalc(Byte...) in Main and method doCalc(long...) in Main match

so, compiler is unable to decide which is better to call.

My understanding is that compiler sees a variable arity methods in both cases (third phase is aplied), but neither is more specific - the compiler cannot decide between autoboxing to Byte or widening to long. The Byte class is not a subtype of long, and long is not a subtype of Byte.

Similar reasoning applies to third example, the only difference is that there is no widening. But compiler still sees both methods as equally specific, and cant decide which one to call.

References: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5

marcinj
  • 48,511
  • 9
  • 79
  • 100