错误的一般类型转换上没有ClassCastException [Java]

时间:2019-02-21 01:08:14

标签: java generics casting classcastexception raw-types

看看以下类的主要方法:

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对象(意味着tInteger类型)可以在String中存储Integer t !!

另一件事:看a2。尝试访问a2.a.t将产生ClassCastException。这让我感到惊讶,因为在访问期间甚至没有执行任何强制转换-至少,这就是我的想法...

所以我的问题简而言之是:

  1. 为什么可以创建3(/ 4)?
  2. 为什么B对象能够在其通用Integer变量t中存储字符串?
  3. 为什么调用5在运行时失败?

谢谢!! :)

1 个答案:

答案 0 :(得分:2)

为什么可以创建3(/ 4)?

创建3是用于推断类型的“钻石”语法。因此,new A<>与编写new A<String>相同。

因此,我们可以将其替换为Java 1.7之前的长语法。

A<String> a1 = new A<String>("str");

这将通过以下构造函数,其中Tjava.lang.String,而tjava.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.tt的类型在编译时均为T,并被java.lang.Object擦除。因此它可以编译而不会发出警告,并且在运行时没有强制检查的内容。分配成功。

创作4正在使用原始类型。您的编译器应发出警告(使用-Xlint)。忽略这些警告可能会在以后导致ClassCastException。泛型是在真实虚拟机上的编译器“虚构”,其本质上遵循Java 1.0语言的规则。

为什么B对象能够在其通用Integer变量t中存储字符串?

t中(因此A)中B的擦除类型为java.lang.Object,因此如果没有检查或检查不正确,则可以存储任何引用类型。

为什么调用5在运行时失败?

a2.a.t处的对象是java.lang.Integera2.a的静态类型为A<java.lang.String>,因此a2.a.t的静态类型为java.lang.Stringjavac将插入一个强制类型转换,以检查返回的原始类型是否符合静态类型。在这种特殊情况下,IIRC,旧版本的javac会错误地忽略强制转换,而使用println(java.lang.Object)重载而不是println(java.lang.String)

您可以使用javap -c -private来准确查看checkcast指令的放置位置。

结论

确保已打开警告。缺省情况下,任何未启用它们的操作都值得怀疑。不要忽略或禁止显示警告,尤其是有关原始类型的警告。