19

なぜ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)
4

4 に答える 4

13

これは型消去によるものです。コンパイル時、コンパイル時

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

ChildAのサブタイプであることを確認するだけParentなので、キャストが確実に失敗することはありません。ChildAtype に割り当てられない可能性があることを考えると、不安定な地面にいるCことを認識しているため、何か問題が発生する可能性があることを知らせる unchecked-cast 警告を発行します。(単にコードを拒否するのではなく、コードのコンパイルを許可するのはなぜですか? Java プログラマーがジェネリック以前の古いコードを最小限の書き換えで移行する必要性に動機付けられた言語設計の選択。)

失敗しない理由については、型パラメーターget()にランタイム コンポーネントがありません。Cコンパイル後、型引数は単にプログラムから消去され、その上限 ( Parent) に置き換えられます。したがって、型引数が と互換性がない場合でも呼び出しは成功しますChildAが、実際に の結果をa キャスト ( fromから)get()として使用しようとすると、例外が発生します。ChildBParentChildB

話の教訓: キャストが常に成功することを自分自身で証明できない限り、未チェックのキャスト例外をエラーとして扱います。

于 2013-03-13T15:59:09.607 に答える
10

型消去: ジェネリックは、(互換性の理由から) コンパイラによって削除され、必要に応じてキャストに置き換えられる構文上の機能にすぎません。

実行時には、メソッドC getは の型を認識しませんC(そのため、インスタンス化できませんnew C())。の呼び出しは、test.<ChildB>get()実際には の呼び出しですtest.get。無制限の型の消去は(その左端の境界)であるため、return (C) new ChildA()に変換されます。次に、as 引数が必要なため、キャストは必要ありません。return (Object) new ChildA()CParentprintlnObject

一方、 はを呼び出す前に にキャストされるtest.<ChildB>get().toString()ため、失敗します。test.<ChildB>get()ChildBtoString()

のような呼び出しmyPrint(test.<ChildB>get())も失敗することに注意してください。Parent返された bygetから型へのキャストは、が呼び出さChildBれたときに行われます。myPrint

public static void myPrint(ChildB child) {
  System.out.println(child);
}
于 2013-03-13T16:03:36.340 に答える
6

生成されたバイトコードを見てください。

12  invokevirtual Test.get() : Test$Parent [30]
15  invokevirtual java.io.PrintStream.println(java.lang.Object) : void [32]
18  getstatic java.lang.System.out : java.io.PrintStream [24]
21  aload_1 [test]
22  invokevirtual Test.get() : Test$Parent [30]
25  checkcast Test$ChildB [38]
28  invokevirtual Test$ChildB.toString() : java.lang.String [40]
31  invokevirtual java.io.PrintStream.println(java.lang.String) : void [44]

への最初の呼び出しは呼び出しのバージョンをprintln使用するだけなので、キャストは必要ありません。Object

于 2013-03-13T16:03:46.073 に答える
5

コンパイル時の型チェックがチェックされていないキャストによって回避される場合、JLS の読み取りからは、実行時の型チェックがいつ発生するかが不明です。コンパイラは型が適切であると想定することが許可されており、ランタイム チェックを可能な限り遅らせることができると思います。これは、各コンパイラの特異性に依存するため、プログラムの動作が明確に定義されていないため、悪いニュースです。

どうやら、コンパイラは最初のものを次のように変換しprintlnます

Parent tmp = test.<ChildB>get();  // ok at runtime
System.out.println(tmp);

それを行うためにコンパイラに過ちを犯すことはできません。それは完全に合法です。

コンパイラは、コードを次のように変換することもできます。

ChildB tmp = test.<ChildB>get();  // fail at runtime
System.out.println(tmp);

したがって、このような単純なプログラムの場合、実行時の動作は JLS によって定義されていません。


2 番目の動作printlnも未定義です。toString()コンパイラは、それがスーパークラスからのメソッドであると推測するのに問題がないため、サブクラスへのキャストは必要ありません。

Parent tmp = test.<ChildB>get();  
String str = tmp.toString();
System.out.println(str);
于 2013-03-13T16:30:00.587 に答える