Consider the following code:
public class Converter { public <K> MyContainer<K> pack(K key, String[] values) { return new MyContainer<>(key); } public MyContainer<IntWrapper> pack(int key, String[] values) { return new MyContainer<>(new IntWrapper(key)); } public static final class MyContainer<T> { public MyContainer(T object) { } } public static final class IntWrapper { public IntWrapper(int i) { } } public static void main(String[] args) { Converter converter = new Converter(); MyContainer<IntWrapper> test = converter.pack(1, new String[]{"Test", "Test2"}); } }
The above code compiles without problems. However, if one changes String[]
to String...
in both pack
signatures and new String[]{"Test", "Test2"}
to "Test", "Test2"
, the compiler complains about the call to converter.pack
being ambiguous.
Now, I can understand why it could be considered ambiguous (as int
can be autoboxed into an Integer
, thus matching the conditions, or lack thereof, of K
). However, what I can't understand is why the ambiguity isn't there if you're using String[]
instead of String...
.
Can someone please explain this odd behavior?
Your 1st case is pretty straight-forward. The below method:
public MyContainer<IntWrapper> pack(int key, Object[] values)
is an exact match for arguments - (1, String[])
. From JLS Section 15.12.2:
The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion
Now, there is no boxing involved while passing those parameters to the 2nd method. As Object[]
is a super type of String[]
. And passing String[]
argument for Object[]
parameter was a valid invocation even before Java 5.
In your 2nd case, since you have used var-args, the method overloading resolution will be done using both var-args, and boxing or unboxing, as per the 3rd phase explained in that JLS section:
The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.
Note, the 2nd phase is not applicable here, due to the use of var-args:
The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation.
Now what is happening here is compiler is not inferring the type argument correctly* (Actually, it's inferring it correctly as the type parameter is used as formal parameter, see the update towards the end of this answer). So, for your method invocation:
MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");
compiler should have inferred the type of K
in generic method to be IntWrapper
, from the LHS. But it seems like it is inferring K
to be an Integer
type, due to which both your methods are now equally applicable for this method call, as both of the requires var-args
or boxing
.
However, if the result of that method is not assigned to some reference, then I can understand that compiler cannot infer proper type as in this case, where is is perfectly acceptable to give an ambiguity error:
converter.pack(1, "Test", "Test2");
May be I guess, just to maintain consistency, it is also marked ambiguous for the first case. But, again I'm not really sure, as I haven't found any credible source from JLS, or other official reference which talks about this issue. I'll keep on searching, and if I get to find one, will update the answer.
If you change the method invocation to give explicit type information:
MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");
Now, the type K
will be inferred as IntWrapper
, but since 1
is not convertible to IntWrapper
, that method is discarded, and 2nd method will be invoke and it will work perfectly fine.
Frankly speaking, I really don't know what is happening here. I would expect the compiler to infer the type parameter from the method invocation context in the first case also, as it works for following problem:
public static <T> HashSet<T> create(int size) { return new HashSet<T>(size); } // Type inferred as `Integer`, from LHS. HashSet<Integer> hi = create(10);
But, it is not doing in this case. So this can possibly be a bug.
*Or may be I don't understand exactly how the Compiler infers the type arguments, when the type is not passed as an argument. So, for learning more about this, I tried going through - JLS §15.12.2.7 and JLS §15.12.2.8, which is about how compiler infers the type argument, but that is going completely over the very top of my head.
So, for now you have to live with it, and use the alternative (providing explicit type argument).
As finally explained in comment by @zhong.j.yu., compiler only applies section 15.12.2.8 for type inference, when it fails to infer it as per 15.12.2.7 section. But here, it can infer the type as Integer
from the argument being passed, as clearly the type parameter is a format parameter in the method.
So, yes compiler correctly infers the type as Integer
, and hence the ambiguity is valid. And now I think this answer is complete.
Here you go, the difference between the below two methods: Method 1:
public MyContainer<IntWrapper> pack(int key, Object[] values) { return new MyContainer<>(new IntWrapper("")); }
Method 2:
public MyContainer<IntWrapper> pack(int key, Object ... values) { return new MyContainer<>(new IntWrapper("")); }
Method 2 is as good as
public MyContainer<IntWrapper> pack(Object ... values) { return new MyContainer<>(new IntWrapper("")); }
That is why you get an ambiguity..
EDIT Yes I want to say that they are the same for the compile. The whole purpose of using variable arguments is to enable a user to define a method when he/she is not sure about the number of arguments of a given type.
So if you are using an object as variable arguments, you just say the compiler that I am not sure how many objects I will send and on the other hand, you are saying,"I am passing an integer and unknown number of objects". For the compiler the integer is an object as well.
If you want to check the validity try passing an integer as the first argument and then pass a variable argument of String. You will see the difference.
For e.g.:
public class Converter { public static void a(int x, String... y) { } public static void a(String... y) { } public static void main(String[] args) { a(1, "2", "3"); } }
Also, please do not use the arrays and variable args interchangeably, they have some different purposes altogether.
When you use varargs the method doesn't expect an array but different parameters of the same type, which can be accessed in an indexed manner.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With