在 Java 中转换的惰性类?

2022-09-02 12:27:00

有人可以启发我为什么我没有在这个片段中得到一个吗?我对为什么它没有像我预期的那样工作感到严格感兴趣。在这一点上,我不在乎这是否是糟糕的设计。ClassCastException

public class Test {
  static class Parent {
    @Override
    public String toString() { return "parent"; }
  }

  static class ChildA extends Parent {
    @Override
    public String toString() { return "child A"; }
  }

  static class ChildB extends Parent {
    @Override
    public String toString() { return "child B"; }
  }

  public <C extends Parent> C get() {
    return (C) new ChildA();
  }

  public static void main(String[] args) {
    Test test = new Test();

    // should throw ClassCastException...
    System.out.println(test.<ChildB>get());

    // throws ClassCastException...
    System.out.println(test.<ChildB>get().toString());
  }
}

这是 java 版本、编译和运行输出:

$ java -version
java version "1.7.0_17"
Java(TM) SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
$ javac -Xlint:unchecked Test.java
Test.java:24: warning: [unchecked] unchecked cast
    return (C) new ChildA();
               ^
  required: C
  found:    ChildA
  where C is a type-variable:
    C extends Parent declared in method <C>get()
1 warning
$ java Test
child A
Exception in thread "main" java.lang.ClassCastException: Test$ChildA cannot be cast to Test$ChildB
  at Test.main(Test.java:30)

答案 1

这是由于类型擦除。编译时,编译时

public <C extends Parent> C get() {
  return (C) new ChildA();
}

只需检查这是 的子类型,因此演员绝对不会失败。它确实知道你处于不稳定的地面上,因为它可能无法分配给 类型 ,因此它会发出未经检查的警告,让您知道某些事情可能会出错。(为什么它允许代码编译,而不仅仅是拒绝它?语言设计选择的动机是Java程序员需要以最少的重写来迁移他们的旧的预泛型代码。ChildAParentChildAC

现在至于为什么没有失败:类型参数没有运行时组件;编译后,类型参数只是从程序中删除并替换为其上限()。因此,即使 type 参数与 不兼容,调用也会成功,但是第一次实际尝试将结果用作强制转换(从 到 )时,将发生,并且你会得到一个异常。get()CParentChildAget()ChildBParentChildB

这个故事的寓意是:将未经检查的演员例外视为错误,除非你能向自己证明演员总会成功。


答案 2

类型擦除:泛型只是编译器删除的语法功能(出于兼容性原因),并在需要时由强制转换替换。

在运行时,该方法不知道 的类型(这就是您无法实例化的原因)。的调用实际上是 对 的调用。 转换为,因为无界类型的擦除是(其最左边的边界)。然后,不需要强制转换,因为需要 as 参数。C getCnew C()test.<ChildB>get()test.getreturn (C) new ChildA()return (Object) new ChildA()CParentprintlnObject

另一方面失败,因为在调用 之前被强制转换为 。test.<ChildB>get().toString()test.<ChildB>get()ChildBtoString()

请注意,类似这样的调用也会失败。从 返回的 by 到类型的强制转换在调用时完成。myPrint(test.<ChildB>get())ParentgetChildBmyPrint

public static void myPrint(ChildB child) {
  System.out.println(child);
}

推荐