java - 错误的泛型转换没有 ClassCastException [Java]

标签 java generics casting classcastexception raw-types

看一下以下类的 main 方法:

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!?

不幸的是,creation 3 成功了。 a1.a.t (这是 t 对象的 B 成员)现在将存储字符串“str”!?所以一个B对象(意味着 t 的类型为 Integer )可以存储 String位于Integer t !!?

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

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

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

谢谢!! :)

最佳答案

为什么创造 3 (/4) 是可能的?

Creation 3 是用于推断类型的“钻石”语法。所以new A<>与写作 new A<String> 相同.

因此我们可以用冗长的 Java 1.7 之前的语法来替换它。

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

这将通过以下构造函数,其中 Tjava.lang.Stringtjava.lang.String 。请注意,运行时此代码只有一份副本,其原始类型为 Tjava.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.ttT在编译时和java.lang.Object删除了。因此它会在没有警告的情况下进行编译,并且在运行时无需检查转换。分配成功。

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

为什么 B 对象能够在其通用整数变量 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已放置说明。

结论

确保您已打开警告。任何默认情况下没有打开它们的东西都有些可疑。不要忽略或抑制警告,尤其是有关原始类型的警告。

关于java - 错误的泛型转换没有 ClassCastException [Java],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54797702/

相关文章:

c# - 通用类型转换

java - 将未知对象转换为 boolean 值

scala 中的 java.util.Date 增量不一致

java - AspectJ + @Configurable

scala - 集合中通用多态数据的上下文边界

.net - 如何在 VB.NET 中使用泛型?

r - 如何使用强制转换或其他函数在 R 中创建二进制表

c++ - 不能 dynamic_cast

java - 为什么在调用 nextLine() 方法时不能在 Scanner(System.in) 中输入字符串?

javax.jms 不存在 - 编译 activemq 示例