ジェネリックスと配列
前回の記事 ではジェネリックス型の不変、共変、反変について説明しました。 今回はジェネリクス型のリスト(List<E>)と配列の性質の違いについて説明します。
前回も説明したように、Java のジェネリックス型は不変である一方、配列は共変です。
List<Object> objectList = new ArrayList<String>(); // => ジェネリックス型は不変なのでコンパイルエラー Object[] objectArray = new String[1]; // => 配列は共変なのでコンパイル可能
配列の方はコンパイル可能である一方、共変であるという性質のために型安全性が保障されません。
そのため、以下のコードはコンパイル可能ですが、実行時エラーが発生してしまいます。
Object[] objectArray = new String[1]; String object = objectArray[0]; // => これは問題なし。 objectArray[0] = new Object(); // => String の配列に Object インスタンスを代入しようとして実行時エラーが発生。 // => java.lang.ArrayStoreException がスローされる。
このようにJava においては、配列とジェネリックスを使った List<T>では型の性質に大きな差があります。
具象化とイレイジャ
配列とジェネリックスにはさらに重要な相違点があります。
ジェネリックスは実行時に型情報が削除されるイレイジャ(erasure)として実装されているのに対し、配列は具象化(reified)されています。 すなわち、ジェネリックスは実行時に自らの型情報を知らないのに対して、配列は実行時に自らの型情報を知っています。
この相違によって、一般的にジェネリックスと配列は上手く調和しません。 そのため、下記のような配列は生成することができません(変数の宣言として利用することはできます)。
- ジェネリックス型の配列:
new List<E>[]
- パラメータされた型の配列:
new List<String>[]
- 型パラメータの配列:
new E[]
E、List<E>、List<String> などは具象化不可能型と呼ばれ、一般的にジェネリックス型はこの具象化不可能型になります。
ただし、項目23で説明した非境界ワイルドカード型は具象化可能で、非境界ワイルドカード型の配列(new List<?>[]
)の生成は許されています。
配列よりリストを選ぶ
ジェネリックス型の配列を生成できないことは以下のような場面で不都合となります。
このように、配列とジェネリックス型のリストはうまく調和しません。 多くの場合、配列よりもリストを使うべきです。
感想
今更だけど "invariant" の訳は「不変」なのか「非変」なのか、どちらがよいのだろう。 Effective Java では「不変」となっているから、このブログでは「不変」にしている。
「不変」だと不変オブジェクトとかの不変(immutable)とかぶってしまうので問題になるのだが、Effective Java では "invariant" の「不変」には不変というように下線が引かれていて区別できるようになっていた。