我对以下代码感到困惑:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class GenericsTest<T extends List> {
public void foo() {
T var = (T) new LinkedList();
}
public static void main(String[] args) {
GenericsTest<ArrayList> gt1 = new GenericsTest<ArrayList>();
gt1.foo();
System.out.println("Done");
}
}
T
的运行时类型似乎是java.util.List
,无论我传递给构造函数的Type参数是什么。
那么为什么编译器在分配var时需要转换为T
?它不应该在编译时知道LinkedList
可以分配给List吗?
我理解代码是假的,我理解为什么它在运行时工作,即使它看起来不应该。令我困惑的部分是为什么编译器要求我在进行赋值时键入(T)?然而,如果没有伪造的话,它就可以很好地编译。
据推测,编译器理解擦除。似乎编译器应该能够在没有强制转换的情况下编译代码。
答案 0 :(得分:11)
LinkedList
可分配给List
,但可能无法分配给T
。
在某些方面,您的测试代码不应该工作 - 它只能起作用,因为编译器有效地删除了强制转换,因为它在执行时没用(由于类型擦除)。它在编译时非常有用,因为它要求您(开发人员)向编译器承诺您正在执行无法检查的有效内容。碰巧,你正在做什么不是有效,但是编译器无法知道这一点,并且在没有更多信息的情况下无法在执行时检查它。您可以提供以下信息:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class GenericsTest<T extends List> {
Class<T> clazz;
public GenericsTest(Class<T> clazz) {
this.clazz = clazz;
}
public void foo() {
T var = clazz.cast(new LinkedList());
}
public static void main(String[] args) {
GenericsTest<ArrayList> gt1 =
new GenericsTest<ArrayList>(ArrayList.class);
gt1.foo();
System.out.println("Done");
}
}
现在这将失败并出现相应的异常 - 类型信息在执行时仍然可用,我们正在使用它以安全的方式执行转换。
请注意,如果使用适当的警告进行编译,编译器会告诉您原始代码实际上并未检查任何内容。
有关此主题和许多其他主题的更多信息,请查看非常全面的Java Generics FAQ。
答案 1 :(得分:7)
海报在评论中问道,
然而,大概是编译器知道的 这就是为什么它需要有 转为(T)。有什么办法吗? 演员可能会失败?
此演员表不会失败。但是编译器警告这个代码会设置一个时间炸弹,以便用ClassCastException
炸毁其他地方。
在示例中,没有理由使用泛型,因为API都不使用类型变量T
。看看更真实的泛型应用。
public class GenericsTest<T extends List> {
3 public T foo() {
4 T var = (T) new LinkedList();
5 return var;
6 }
8 public static void main(String... argv) {
9 GenericsTest<ArrayList> gt1 = new GenericsTest<ArrayList>();
10 gt1.foo();
11 System.out.println("Test one okay");
12 ArrayList<?> list = gt1.foo();
13 System.out.println("Test two okay");
14 }
}
在第12行抛出ClassCastException
ClassCastException
,没有演员?调用代码完全正确。无效的强制转换(bug)位于调用方法的第4行。但这个例外是在某个时间和地点提出的。
Java泛型的目的是确保代码是类型安全的。如果代码的所有编译时没有“未选中”警告,则保证在运行时不会引发ClassCastException
。但是,如果你所依赖的库写得不正确,就像这个例子那样,承诺就会被破坏。
答案 2 :(得分:1)
那是因为type erasure;在编译时,泛型参数变为其下限。
编译器试图告诉你出了问题。如果它按照您认为应该执行的操作,那么您将获得ClassCastException
- 您无法将LinkedList
投射到ArrayList
。
答案 3 :(得分:0)
我以为你是来自C#?在这种情况下,Java泛型不是C#泛型。
当您提供泛型类型参数时,Java不生成强类型类,它只使用最小公用命名类型,该类型可用于可能提供的所有可能的泛型类型参数组合。
因此,在这种情况下,您使用List
,因为您指定了extends List
。