假设我们有以下代码:
class A {}
class B extends A {}
class C extends B {}
public static <T> T testMe(List<? super T> list1,List<? extends T> list2) {
return null;
}
public static void main(String[] args) {
List<B> listB = new ArrayList<>();
List<C> listC = new ArrayList<>();
// All three variants are possible:
A a=testMe(listB, listC);
B b=testMe(listB, listC);
C c=testMe(listB, listC);
}
问题是关于public static <T> T testMe(List<? super T> list1,List<? extends T> list2)
。如果有三个类,编译器如何确定T
类型:A,B,C,
?当我分析Collections.copy
时,就出现了这个问题。
答案 0 :(得分:4)
编译器在所有3种情况下都推断出类型参数C
的类型T
。
适合约束的是most specific type。
[T]他的推理算法试图找到适用于所有参数的最具体的类型。
对于前两个陈述,
A a = testMe(listB, listC);
B b = testMe(listB, listC);
B
和C
都匹配,因为List<B>
匹配List<? super B>
且List<C>
匹配List<? extends B>
,List<B>
匹配List<? super C>
和List<C>
匹配List<? extends C>
。编译器选择匹配的最具体类型C
。
您可以使用显式类型参数进行编译,以使编译器将其解析为B
:
A a = Super.<B>testMe(listB, listC);
B b = Super.<B>testMe(listB, listC);
在第三行中,只有C
匹配,因此这是编译器为T
选择的内容。
C c = testMe(listB, listC);
这是因为分配的变量属于C
类型,而B
无法分配给C
。
答案 1 :(得分:1)
如果您将签名更改为需要T
的令牌,很快就会发现正在发生的事情:
public static <T> T testMe(Class<T> c, List<? super T> list1, List<? extends T> list2) {
return null;
}
public static void main(String[] args) {
List<B> listB = new ArrayList<>();
List<C> listC = new ArrayList<>();
A a = testMe(A.class, listB, listC); // compile error
B b = testMe(B.class, listB, listC); // OK. T == B
C c = testMe(C.class, listB, listC); // OK. T == C
}
在您的示例中编译的原因:
A a = testMe(listB, listC);
是因为T
被推断为B
(或C
- 无关紧要),但B
也是A
的实例,因此,可以将类B
的对象分配给A
类型的变量。
答案 2 :(得分:0)
从纯理论的角度来看,为什么类型论证是什么重要?该类型未编码到编译的字节码中。它唯一的相关性是编译时类型检查是否通过。为此,如果编译器能够保证某些选择类型参数使得代码编译,那么哪一个以及是否有更多应该是不相关的。在前两种情况中,存在多种类型(B
和C
);编译器无需决定一个。在第三种情况下,只有一种类型(C
)有效。在所有情况下,如果编译器认为至少有一种类型有效,那么它应该让它编译。</ p>
答案 3 :(得分:-1)
编译器在编译时知道您传递的Object的类型是什么,例如List<B>
,因为泛型是编译时功能。它可以使用此信息来检查类型错误。在编译之后,由于类型擦除而遗漏了泛型,代码实际上就是这样:
public static Object testMe(List<Object> list1, List<Object> list2) {
//some code
return null;
}