Unanticipated behavior for Generics and Var-Arg
public static <T> void method(T singleVal, T ... vals) {
System.out.println(singleVal);
for(T val : vals) {
System.out.println(val);
}
}
called via:
method(0, new int[]{1,2,3});
//method('a', new char[] {'b','c','d'}); //same behavior
Prints:
0
[I@7699a589 //<--- an array at element 0
It happens only when passing it a primitive[].
With int[] in, T becomes both Integer and int [] simultaneously?
When passing it an Integer[] instead of int [], it behaves as anticipated:
method(0, new Integer[]{1,2,3});
//method('a', new Character[] {'b','c','d'}); //same behavior
0
1
2
3
CodePudding user response:
I believe the answer comes from JLS §15.12.4.2. Evaluate Arguments. We've already done overload resolution trivially (there's only one overload in this example), so we know the target method. It's just a matter of evaluating the arguments and making the call.
The process of evaluating the argument list differs, depending on whether the method being invoked is a fixed arity method or a variable arity method (§8.4.1).
If the method being invoked is a variable arity method
m, it necessarily hasn > 0formal parameters. The final formal parameter ofmnecessarily has typeT[]for someT, andmis necessarily being invoked withk ≥ 0actual argument expressions.If
mis being invoked withk ≠ nactual argument expressions, or, ifmis being invoked withk = nactual argument expressions and the type of thek'th argument expression is not assignment compatible withT[], then the argument list(e1, ..., en-1, en, ..., ek)is evaluated as if it were written as(e1, ..., en-1, new |T[]| { en, ..., ek }), where|T[]|denotes the erasure (§4.6) ofT[].
Now let's see how this word soup applies to your example. The method in question is
public static <T> void method(T singleVal, T ... vals)
And the argument list is
method(0, new int[]{1,2,3});
The method m has two formal arguments, of types T and T[]. Since the final argument is of array type and is marked with the variable-arity designator ..., this is a variable arity method. We're calling it with two arguments, so k = n in the above quote. Now, the first argument is of type int, which is not valid as a generic argument, so autoboxing takes that to Integer. That means T is Integer. The second argument is int[]. Since k = n, we're going to wrap this in an array if (and only if) our second argument is not assignment compatible with Integer[]. And the second argument is int[], not Integer[]. Autoboxing won't convert a whole array, so we assume we need to wrap this.
Now, you reasonably ask why this function call can happen at all. After all, we already decided T was Integer, and now we're saying T is int[]? Well, I lied. Or, rather, I oversimplified. We didn't decide T was Integer. We decided T was compatible with Integer. Basically, we decided that T was some supertype of Integer. In the absence of any additional information, Java would conclude that T should be Integer, but it is also happy to widen that during type inference if needed.
In your case, we've already decided that int[] is not compatible with Integer[] (or with U[] for any supertype U of Integer, to be precise), since int is a primitive and we can't autobox on the inside of an array. So we've already made the decision that this is a variable argument call that will be wrapped in an array. That is, the call is getting converted to
method(new Integer(0), new int[][]{new int[]{1, 2, 3}});
It's just a matter of finding a T that works with the method signature
// Note: No T... now, we've already made that decision, so it's T[] now.
public static <T> void method(T singleVal, T[] vals)
That is, we need a type T that is a common supertype of both Integer and int[]. Integer has several supertypes: Number, Comparable<Integer>, Serializable, etc. But only one type is a supertype of both Integer and an array type: Object. That is, you've made a call with T = Object.
The lesson here, I think, is that generics and varargs don't play nice, an when autoboxing gets in the mix, they definitely don't play nice. Personally, I only use variable arguments with concrete types (so int... or String... are fair game, but I'd never write T...), just to avoid messy business like this.

