存在以下程序
1 | public class ExampleUnitTest { |
问题:
1 | typedValue = (T) value; |
是否会出错。
答案是不会的。事实上,最终的执行结果如下
ExampleUnitTest$A@15db9742
Exception in thread “main” java.lang.ClassCastException: ExampleUnitTest$A cannot be cast to ExampleUnitTest$B
at ExampleUnitTest.main(ExampleUnitTest.java:4)
requestUpdate中的println顺利被println出来了,但是类型却不是B的,也是未指定的,而是A的;另一方面,在main中的将requestUpdate的结果赋值给B类型变量,则会发生转换错误。
反编译以上代码,可以得到
1 | public class ExampleUnitTest { |
其中 requestUpdate 方法中 第0,3,4,5,8 对应源码 “Object value = new A();” ,最终存放在local variable 的0号位置,可以看到,“ T typedValue = (T) value;” 只执行了9,10号片段,作用为
- 从0号位置读取变量
- 将其存放于1号位置(相当于将value赋值于 typedValue)
最后从1号位置读取变量,return。可见,从头至尾都没有对类型进行转换,更谈不上检查。
而main方法的处理则不一样,看”B b = requestUpdate();”这一句对应的编译后代码
1 | // Method requestUpdate:()Ljava/lang/Object; |
后边的注释先写得比较明白了,最关键的是3号位置字节码,”checkcast“显示就是用于类型转换时的检查的,至于里边做了什么,这里看不到,oracle是这么写的:
Operation
Check whether object is of given type
也就是我们常规理解上的类型检查了,但就是因为这个区别,导致了带明确类型的变量值赋值给泛型类型(T)时,没有发生错误(事实上这么说也不严谨,泛型发生在编译阶段,类型擦除后运行时也没有所谓的”泛型类型“,这里指代T),因为根本不会(也不可能)在这一次的赋值时作类型检查,它根本不知道等号左边是真正的类型是什么,所以上面requestUpdates内的赋值才不会有问题,而main中的就崩了。
以上是基于编码后的字节码所作的一点分析,可能还存在其他的解释。泛型是发生在编译时的,而”T typedValue = (T) value;” 是肯定可以通过编译的(字面意思上的强转给同一个类型变量),所以编译检查通过,运行时也不会出错,但typedValue在内存中表示的类型可能就不是我们期待的类型。