泛型和 Var-Arg 的意外行为
public static <T> void method(T singleVal, T ... vals) {
System.out.println(singleVal);
for(T val : vals) {
System.out.println(val);
}
}
调用方式:
method(0, new int[]{1,2,3});
//method('a', new char[] {'b','c','d'}); //same behavior
打印:
0
[I@7699a589 //<--- an array at element 0
仅当向其传递原语[]时才会发生这种情况。
使用 int[] 时,T
变为两者 Integer
和 int []
同时?
当向其传递 Integer[] 而不是 int [] 时,它的行为如预期:
method(0, new Integer[]{1,2,3});
//method('a', new Character[] {'b','c','d'}); //same behavior
0
1
2
3
最佳答案
我相信答案来自JLS §15.12.4.2. Evaluate Arguments 。我们已经简单地完成了重载解析(本示例中只有一个重载),因此我们知道目标方法。这只是评估参数并做出决定的问题。
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[]
.
现在让我们看看这个单词 soup 如何应用于您的示例。有问题的方法是
public static <T> void method(T singleVal, T ... vals)
参数列表是
method(0, new int[]{1,2,3});
方法m
有两个形式参数,类型为 T
和T[]
。由于最后一个参数是数组类型,并且用变量数指示符 ...
进行标记。 ,这是一种变数方法。我们用两个参数调用它,所以 k = n
在上面的引用中。现在,第一个参数的类型为 int
,它作为通用参数无效,因此自动装箱将其设置为 Integer
。这意味着T
是 Integer
。第二个参数是int[]
。自 k = n
,如果(且仅当)我们的第二个参数不与Integer[]
兼容,我们将把它包装在一个数组中。 。第二个参数是 int[]
,不是Integer[]
。自动装箱不会转换整个数组,因此我们假设需要对其进行包装。
现在,您可以合理地询问为什么会发生此函数调用。毕竟我们已经决定了T
是Integer
,现在我们说 T
是 int[]
?好吧,我撒谎了。或者说,我过于简单化了。我们没有决定 T
是Integer
。我们决定T
与兼容 Integer
。基本上,我们决定 T
是某种父类(super class)型 Integer
。在没有任何附加信息的情况下,Java 会得出结论:T
应该是Integer
,但如果需要的话,它也很乐意在类型推断期间扩大范围。
就您而言,我们已经决定 int[]
与 Integer[]
不兼容(或者准确地说,对于 U[]
的任何父类(super class)型 U
使用 Integer
),因为 int
是一个原语,我们不能在数组内部自动装箱。所以我们已经决定这是一个将包装在数组中的变量参数调用。也就是说,调用被转换为
method(new Integer(0), new int[][]{new int[]{1, 2, 3}});
这只是找到一个T
的问题与方法签名一起使用
// Note: No T... now, we've already made that decision, so it's T[] now.
public static <T> void method(T singleVal, T[] vals)
也就是说,我们需要一个类型T
这是 Integer
的共同父类(super class)型和int[]
。 Integer
有几个父类(super class)型:Number
, Comparable<Integer>
, Serializable
等等。但只有一种类型是两者的父类(super class)型Integer
和数组类型:Object
。也就是说,您已调用 T = Object
电话.
我认为,这里的教训是,泛型和可变参数不能很好地发挥作用,当自动装箱混合在一起时,它们肯定不能很好地发挥作用。就我个人而言,我只使用具有具体类型的变量参数(因此 int...
或 String...
是公平的游戏,但我永远不会写 T...
),只是为了避免这样的困惑。
关于java - Var-Arg 方法同时将泛型类型推断为 Object 和 Primitive[],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73428585/