假设我们有以下泛型类
public class SomeType<T> {
public <E> void test(Collection<E> collection){
System.out.println("1st method");
for (E e : collection){
System.out.println(e);
}
}
public void test(List<Integer> integerList){
System.out.println("2nd method");
for (Integer integer : integerList){
System.out.println(integer);
}
}
}
现在在main方法中我们有以下代码片段
SomeType someType = new SomeType();
List<String> list = Arrays.asList("value");
someType.test(list);
执行someType.test(list)
后,我们将获得&#34;第二种方法&#34;在我们的控制台以及java.lang.ClassCastException
中。据我了解,为什么执行第二个test
方法的原因是我们不使用泛型SomeType
。因此,编译器会立即从类中删除所有泛型信息(即<T>
和<E>
)。完成后,第二个test
方法将List integerList
作为参数,当然List
与List
的匹配优于Collection
。
现在考虑在main方法中我们有以下代码片段
SomeType<?> someType = new SomeType<>();
List<String> list = Arrays.asList("value");
someType.test(list);
在这种情况下,我们将获得&#34;第一种方法&#34;在控制台中。这意味着第一个测试方法正在执行。问题是为什么?
根据我对运行时的理解,由于类型擦除,我们从来没有任何泛型信息。那么,为什么第二个test
方法无法执行。对我来说,第二个test
方法应该(在运行时)以下列形式public void test(List<Integer> integerList){...}
不是吗?
答案 0 :(得分:4)
适用的方法匹配之前类型擦除(see JSL 15.12.2.3)。 (擦除意味着运行时类型没有参数化,但是在编译时选择了方法,当类型参数可用时)
list
的类型为List<String>
,因此:
test(Collection<E>)
适用,因为List<Integer>
与Collection<E>
兼容,其中E
为Integer
(正式) ,约束公式List<Integer> → Collection<E> [E:=Integer]
缩减为true
,因为List<Integer>
是Collection<Integer>
的子类型。
test(List<String>)
不适用,因为List<String>
与List<Integer>
不兼容(正式来说,约束公式List<String>
→List<Integer>
会缩减为{ {1}}因为false
不是String
的超类型。
<{3}}中隐藏了解释的详细信息。
Integer
:
设θ为替换[E:=整数]
[...]
一组约束公式C的构造如下:让F1,...,Fn为m的形式参数类型,让e1,...,ek为调用的实际参数表达式。
在这种情况下,我们有test(Collection<E>)
和F1 = Collection<E>
然后:[约束公式集]包括
在这种情况下,我们有e1 = List<Integer>
(其中→表示在推断出类型变量List<Integer> → Collection<E> [E:=Integer]
后e1与F1兼容)
对于E
,没有替换(因为没有推理变量),约束只是test(List<String>)
→List<String>
。
答案 1 :(得分:3)
The JLS is a bit of a rat's nest on this one,但您可以使用非正式(他们的话,不是我的)规则:
如果第一个方法处理的任何调用都可以传递给另一个没有编译时错误的调用,那么[O] ne方法比另一个方法更具体。
为了论证,让我们调用<E> test(Collection<E>)
方法1和test(List<Integer>)
方法2。
让我们在这里抛出一个扳手 - 我们知道整个类都是通用的,所以在没有某种类型的情况下对它进行实例化会产生... 不太理想在运行时进行类型检查。
另一部分原因是List
比Collection
更具体,如果方法通过List
,它将更容易适应一个Collection
,但需要注意的是在编译时应检查该类型。由于不是原始类型,我相信会跳过此特定检查,并且Java将List<Integer>
视为更具体而不是{{ 1}}。
您应该向JVM提交错误,因为这似乎不一致。或者,至少,让那些写过JLS的人解释为什么这比合适的数学符号更好的英语是合法的......
继续前进;在第二个例子中,您给我们礼貌地键入您的实例作为通配符,这允许Java进行正确的编译时断言Collection<capture(String)>
是最安全的方法。< / p>
请注意,这些检查都不会在运行时发生。这些都是在Java运行之前做出的所有决策,因为模糊的方法调用或对具有不受支持的参数的方法的调用会导致编译时错误。
故事的道德:不使用原始类型。它们是邪恶的。它使类型系统以奇怪的方式运行,而且它只是为了保持向后兼容性。