6

C++ テンプレートと C# ジェネリック、およびそれらが Java の型消去されたジェネリックとどのように異なるかについての議論を読んでいました。たとえば、コレクションを扱う場合など、Java はまだ実行時にキャストを使用しているという声明を読みました。これが本当なら、私はそれを知りませんでした!

次のようなコードがあるとしましょう。

ArrayList<SomeClass> list = new ArrayList<SomeClass>();
...
SomeClass object = list.get(0);

私の質問はです。これは効果的にコンパイルされていますか

ArrayList list = new ArrayList();
...
SomeClass object = (SomeClass) list.get(0);

もしそうなら、なぜですか?リストが型であるという事実はArrayList<SomeClass>、コンパイル時と実行時に、ArrayList 内に SomeClass のみが格納されることを保証すると思いましたか? または、安全でない型キャストを行って を に変換できますArrayList<OtherClass>ArrayList<SomeClass>?

Javaジェネリックで実行時の型キャストが行われる他の機会はありますか?

最後に、実行時のキャストが実際に使用されている場合、JIT が実行時のキャスト チェックを省略できる場合はありますか?

(マイクロ最適化には価値がない、プリエンプティブな最適化は諸悪の根源である、などの回答/コメントは控えてください。他の同様の質問でこれらを目にします。これらの点はよく理解されていますが、試すポイントを奪うものではありません型消去されたジェネリックが内部でどのように実装されているかを理解するために。)

4

5 に答える 5

5

あなたの仮定は正しいです。

次の正当なコードを記述できるため、型チェックは常に必要です。

ArrayList<X> good = new ArrayList<X>();
ArrayList q = x;
ArrayList<Y> evil = (ArrayList<Y>)q;  //Doesn't throw due to type erasure
evil.add(new Y()); //this will actually succeed
X boom = good.get(0);

ArrayListfrom からto へのキャストArrayList<Y>は (常に) 未チェックのキャスト警告を出しますが、実行時には (常に) 成功します。

于 2012-04-12T15:03:54.837 に答える
1

Java は、バージョン間の互換性を維持するために素晴らしい仕事をしています。つまり、Java5+ 構文を 1.4 互換クラスにコンパイルできます。これが、実行時に SomeClass のみが ArrayList 内に格納されることを ArrayList が保証しない理由の一部だと思います。

また、ジェネリクス コードが実際にコンパイルされたものを確認するには、優れたJADツールを使用してください。

あなたのコードは実際にコンパイルされますSomeClass object = (SomeClass) list.get(0);

于 2012-04-12T15:10:14.027 に答える
1

list が ArrayList 型であるという事実は、コンパイル時および実行時に、 SomeClass のみが ArrayList 内に格納されることを保証すると思いましたか? または、ArrayList を ArrayList に変換するために安全でない型キャストを行うことはできますか?

はい、型情報は実行時に破棄されます。

ArrayList<String>これは、 type のオブジェクトがあるのか​​ type のオブジェクトがあるのか​​を理解できないことを意味しますArrayList<Long>。JVM はジェネリック クラスのインスタンスの実際の型引数を認識しないため、その実行時チェックは行われません (また、そのデータを使用する場合にのみエラーが表示されます)。

このコードは有効です:

public void enqueueItem(ArrayList<Integer> list, Integer item) {
    List listAlias = list;
    listAlias.add(x.toString());
}

実行時には問題はありません。型チェックは問題を回避するために引き続き存在し、古いコードと新しいコードを相互運用することができます。.NET 環境でも同じ動作をします (IList<T>から派生IList)。

支払うのは、キャストによるパフォーマンスへの影響のみです (これが、Java の汎用実装との大きな違いです)。多かれ少なかれジェネリックは、エラーを検出し、コードをより読みやすくするためのコンパイル時のヘルパーです。たとえば、次のコードはコンパイラにとって非常に有効です。

public ArrayList getCollection() {
   return new ArrayList<Integer>();
}

public void doStuff() {
   ArrayList<String> list = getCollection();
   list.add("text");
}

Javaジェネリックで実行時の型キャストが行われる他の機会はありますか?

はい、それらを消費するたびに(ジェネリックで何か悪いことをするとエラーが発生するポイントです)。

最後に、実行時のキャストが実際に使用されている場合、JIT が実行時のキャスト チェックを省略できる場合はありますか?

いいえ、常にキャストを行いますが、これは非常に実装の詳細であり、とにかくサブクラスの型引数を追跡するため、ジェネリック クラスから独自のクラスを派生させると、この問題から救われます。

もしそうなら、なぜですか?

一部のインターフェイスを変更しても、既存のコードはすべて新しいジェネリックで機能します。これは、段階的にジェネリックに更新できることを意味します。パフォーマンスへの影響については心配していません (私たちはこれで長い間生き残ってきましたが、今は問題になっているのでしょうか?) しかし、このトリックはすべて好きではありません。たぶん、結局のところ、オークをいっぱいにして妻に飲んでもらうことはできません...

于 2012-04-12T15:48:18.757 に答える