看看下面这个类的主要方法:
public class Outer {
static class A<T> {
public A<T> a;
public T t;
public A() { a = null; }
public A(T t) {
a = (A<T>) new B();
a.t = t;
}
}
static class B extends A<Integer> {}
public static void main(String[] args) {
// A<String> c = new B(); // CREATION 1: would not work
// A<String> b = (A<String>) new B(); // CREATION 2: would not work
A<String> a1 = new A<>("str"); // CREATION 3: works (???)
A<String> a2 = new A(0); // CREATION 4: works (???)
System.out.println(a1.a); // CALL 1 -> Poly$B@...
System.out.println(a1.t); // CALL 2 -> null
System.out.println(a1.a.a); // CALL 3 -> null
System.out.println(a1.a.t); // CALL 4 -> str (???)
System.out.println(a2.a.t); // CALL 5 -> ClassCastException (???)
}
}
显然,创建1和2在编译时会失败,因为无法进行强制转换。第三个对象创建是可能的,所以我没想到它会在编译时失败,但是:我希望(A<T>) new B();
在运行时失败,因为B
扩展A<Integer>
并且不应该将A<Integer>
强制转换为A<String>
(与创建2相同) )...但是这里没有ClassCastException!?
不幸的是,创作3有效。 a1.a.t
(这是t
对象的B
成员)现在将存储字符串“str”!?所以B
对象(意思是t
是Integer
类型)可以在String
中存储Integer t
s !!
另一件事:看看a2
。尝试访问a2.a.t
将产生ClassCastException。这让我感到惊讶,因为在访问期间甚至没有演员表演 - 至少,这是我的想法......
所以我的问题很简单:
谢谢!! :)
为什么创作3(/ 4)可能?
Creation 3是用于推断类型的“菱形”语法。因此new A<>
与写new A<String>
相同。
因此,我们可以使用Java 1.7之前的长语法来替换它。
A<String> a1 = new A<String>("str");
这将通过以下构造函数,其中T
是java.lang.String
,t
是java.lang.String
。注意,在运行时只有一个代码的副本,T
的原始类型是java.lang.Object
。
public A(T t) {
a = (A<T>) new B();
// ^^^^^^ NB: This will at least give a rawtype warning
// as it causes heap pollution.
a.t = t;
}
a
的类型是A<T>
在编译时和java.lang.Object
擦除。 a.t
和t
的类型在编译时是T
并且java.lang.Object
被删除。所以它在没有警告的情况下编译,并且在运行时没有要检查的转换。作业成功。
Creation 4使用原始类型。您的编译器应该发出警告(使用-Xlint
)。忽略这些警告可能会导致ClassCastException
以后。泛型是真实虚拟机上的编译器“虚构”,它基本上遵循Java 1.0语言的规则。
为什么B对象能够在其通用的Integer变量t中存储String?
在t
(以及A
)中删除的B
类型是java.lang.Object
,因此如果没有或不正确的检查,可以存储任何引用类型。
为什么call 5在运行时失败?
在a2.a.t
的对象是java.lang.Integer
。 a2.a
有静态类型A<java.lang.String>
所以a2.a.t
的静态类型是java.lang.String
。 javac
将插入一个强制转换,以检查返回的原始类型是否符合静态类型。在这个特殊情况下,IIRC,旧版本的javac
会错误地省略演员表并使用println(java.lang.Object)
重载而不是println(java.lang.String)
。
您可以使用javap -c -private
查看checkcast
指令的确切位置。
结论
确保已打开警告。默认情况下没有打开它们的任何东西都有些怀疑。不要忽略或禁止警告,尤其是关于原始类型的警告。