10

私はこの期間にジェネリックを勉強していますが、今日、私にとってこの謎を発見しました.

次のダミークラスを考えてみましょう:

    public class Main{

    public static void main(String[] args) { 
        Container<Integer> c = new Container<Integer>(); 

        c.getArray();                      //No Exception
        //c.getArray().getClass();         //Exception
        //int a = c.getArray().length;     //Exception

    } 

}


class Container<T> { 

    T[] array; 

    @SuppressWarnings("unchecked") 
    Container() { 
        array = (T[])new Object[1]; 
    } 

    void put(T item) { 
        array[0] = item; 
    } 

    T get() { return array[0]; } 

    T[] getArray() { return array; }
}  

消去のため、実行時に getArray() メソッドの T[] 戻り値の型が Object[] に変換されます。これは私にとって完全に合理的です。

そのメソッドにそのままアクセスする場合 (c.getArray()) 例外はスローされませんが、返された配列でいくつかのメソッド (c.Array().getClass() など) を呼び出そうとすると、またはc.getArray().length などのフィールドにアクセスすると、次の例外がスローされます。

スレッド「メイン」での例外 java.lang.ClassCastException: [Ljava.lang.Object; [Ljava.lang.Integer; にキャストできません。

この例外がスローされるのはなぜですか? 単純な c.getArray() 呼び出しでもスローされないのはなぜですか? 単に getClass() を呼び出しているか、長さにアクセスしているのに、なぜ Integer[] にキャストしようとしているのですか? getClass() と length は Object[] でも利用できませんか?

あなたの多くの(私は願っています)そして説明的な(私もこれを願っています)答えを前もって感謝します。

4

3 に答える 3

2

例外の理由は、コンパイラInteger[]が を期待しているのにObject[]. の呼び出しサイトにランタイム キャスト - が追加されましたgetArray。これらのキャストは、コンストラクター内の嘘の、ダミーの、効果のないキャストを発見しました。

正確であるためには、インスタンスを作成するために T の実際のクラスが必要です。

@SuppressWarnings("unchecked")
Container(Class<T> type) {
    array = (T[]) Array.newInstance(type, 10);
}


    Container<Integer> c = new Container<Integer>(Integer.class); 

    c.getArray();
    Class<?> t = c.getArray().getClass();
    System.out.println(t.getName());
    int a = c.getArray().length;

また、ここには「安全でない」キャストが残っていますが、次のような n 次元配列の低レベルのメソッドであるためT[]、これは避けられません。Array.newInstance

(double[][][][][][]) Array.newInstance(double.class, 3, 3, 3, 3, 3, 6);
于 2016-07-08T13:25:47.397 に答える
1

安全でないチェックされていないキャストを行うと、どこかで例外が発生する場合と発生しない場合があります。どこかで例外が発生する保証はありません。

この場合、例外が発生するかどうかは、コンパイラが への呼び出しの結果をキャストするために消去されたコードにキャストを挿入したかどうかによって異なりますInteger[]。この場合、最初のケースではなく、2 番目と 3 番目のケースでキャストが挿入されたようです。

3 つのケースのそれぞれで、コンパイラはキャストを挿入できます (結果がキャストを挿入するかどうかを仮定することが許可されているため( 3 つすべてInteger[]でのみ必要な方法で式が使用されているため))。Object[]キャストを挿入するかどうかは、特定のコンパイラの実装によって決定されます。

このコンパイラが最初のケースではキャストを挿入せず、2 番目と 3 番目のケースではキャストを挿入しないのはなぜですか? 明白な説明の 1 つは、最初のケースでは結果が明らかに使用されていないため、キャストが不要であると判断するのは非常に簡単だということです。2 番目と 3 番目のケースでは、キャストが不要であると判断するには、式がどのように使用されているかを調べて、 で動作することを確認する必要がありObject[]ます。これはかなり複雑な分析です。コンパイラの作成者は、結果が使用されない場合にのみキャストをスキップするという単純なアプローチをおそらく選択しました。

別のコンパイラは、3 つのケースすべてにキャストを挿入する可能性があります。また、別のコンパイラでは、3 つのケースすべてでキャストがない場合があります。あなたはそれに頼ることはできません。

于 2016-07-08T18:41:11.740 に答える
1

これが動作であると言っているJLSの正確な場所を見つけることができませんでしたが、理由は次のようなものだと思います:

表現:

c.getArray().getClass();

以下とほぼ同等です。

Integer[] arr = (Integer[]) c.getArray();
arr.getClass();

型消去のためにキャストを追加する必要がある場所。この暗黙のキャストはcheckcastバイトコードに命令を追加しますが、これは型が であるClassCastExceptionため失敗します。c.getArray()Object[]

次のバイトコードを調べます。

static void implicit() {
  Container<Integer> c = new Container<Integer>();
  c.getArray().getClass(); //Exception
}

static void explicit() {
  Container<Integer> c = new Container<Integer>();
  Integer[] arr = (Integer[]) c.getArray();
  arr.getClass(); //Exception
}

我々が得る:

  static void implicit();
    Code:
       0: new           #2                  // class Container
       3: dup
       4: invokespecial #3                  // Method Container."<init>":()V
       7: astore_0
       8: aload_0
       9: invokevirtual #4                  // Method Container.getArray:()[Ljava/lang/Object;
      12: checkcast     #5                  // class "[Ljava/lang/Integer;"
      15: invokevirtual #6                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      18: pop
      19: return

  static void explicit();
    Code:
       0: new           #2                  // class Container
       3: dup
       4: invokespecial #3                  // Method Container."<init>":()V
       7: astore_0
       8: aload_0
       9: invokevirtual #4                  // Method Container.getArray:()[Ljava/lang/Object;
      12: checkcast     #5                  // class "[Ljava/lang/Integer;"
      15: checkcast     #5                  // class "[Ljava/lang/Integer;"
      18: astore_1
      19: aload_1
      20: invokevirtual #6                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      23: pop
      24: return

したがって、explicitバージョンの唯一の違いは、次の 3 つの命令です。

      15: checkcast     #5                  // class "[Ljava/lang/Integer;"
      18: astore_1
      19: aload_1

私が理解している限り、変数にこれを明示的に格納するためだけに存在します。

于 2016-07-08T13:43:40.350 に答える