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 > 0
formal parameters. The final formal parameter ofm
necessarily has typeT[]
for someT
, andm
is necessarily being invoked withk ≥ 0
actual argument expressions.If
m
is being invoked withk ≠ n
actual argument expressions, or, ifm
is being invoked withk = n
actual 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.