Effective Javaを読むチャレンジ-項目25-

項目25 配列よりリストを選ぶ

この項目は単に配列を使うなという話ではなく、型安全性に関するコンパイルエラーや警告が出ているなら、まずは配列をリスト(ジェネリックス)に変えてみなさい、という話です。

配列は共変である

いきなり聞き慣れない用語が出てきました。共変(covariant)とは、SubがSuperのサブタイプであるとき、Sub[]はSuper[]のサブタイプである、ということです。それに対してジェネリックスは不変です。List<Type1>とList<Type2>はどちらかのサブタイプでもどちらかのスーパータイプでもありません。

つまり配列が共変であるということは、サブタイプの配列型からスーパータイプの配列型へ変換できるということです。

上記のコードはコンパイルが通りますが、実行時にjava.lang.ArrayStoreExceptionをスローします。上記のコードではLong以外のオブジェクトを代入することができません。Objectクラスも代入できません。

それに対して、不変であるジェネリックスは異なるタイプへの変換をしようとするとコンパイルエラーになります。

配列とジェネリックスの相違点の1つは、配列は共変で、型の不一致に対して実行時に例外がスローされ、ジェネリックスは不変で型の不一致に対してコンパイル時にエラーが発生するという点だということです。もちろんコンパイル時にエラーが発生する方が良いです。

配列は具象化されている

またよく分からない言葉ですね。この具象化されている(reified)という言葉は、配列が実行時にその要素型を知っていて、強制することを意味しているということです。先ほどの例でいうと、Objectクラスの配列が最初に代入したLongの配列に強制された(具象化された)ため、その後のStringの代入に対して実行時例外をスローしています。

ジェネリックスはイレイジャで実装されている

イレイジャは型消去と呼ばれています。型消去というのは、ジェネリックスではコンパイル時にのみ型の強制を行い、実行時には要素の型情報を廃棄することをいいます。これは項目23で説明したとおり、移行互換性のための仕組みです。ジェネリックスは実行時に原型とキャストを使った従来のコードに変換されます。そうすることで過去のバージョンとの互換性を保っています。

コンパイルされても型情報を残す方法もあるようですが、言語仕様中心に話をすすめる本書を読む上ではとりあえず必要ないですかね・・・。とても勉強になります。

配列とジェネリックスは調和しない

ここからはジェネリック型の配列の話。Javaでは「new List<String>[1]」といったジェネリック型の配列はコンパイルエラーになります。原型であればコンパイルが通ります。このあと、なぜジェネリック型配列が許されていないかということを下記のサンプルコードを使って説明しています。

本書では、この(1)のコンパイルが許されていると仮定すると、最終的にこのコードはClassCastExceptionをスローするよ、だからその例外発生を防ぐためにジェネリック型の配列はコンパイルエラーなんだよ、というように話が進みます。ところがこのコード、(1)の右辺をジェネリック型でなく原型にしてコンパイルが通るようにした後に実行してもClassCastExceptionをスローするんですよね・・・防ぎきれてはいない、と。

これはジェネリック型の型安全性を保証するための仕様で、Listの配列は元々許されてます。本書でもジェネリック型の配列が許されていない理由は型安全のためで、それを許すとジェネリックス型システムが提供する基本的な保証を破ることになるという様なことが書かれています。既存の問題は残っているけど、新しい仕組みの上では防げているよ、ということですね。

具象化不可能型(non-reifiable type)

直訳すると非具象化可能型という感じですが、本書では具象化不可能型と訳されています。具象化とは実行時に型情報が分かっているということでした。その否定である具象化不可能型というのは、実行時に型情報が分からない型ということになりますね。それはジェネリック型の仮型パラメータであるEやジェネリック型のList<E>やList<String>です。ただし、非境界ワイルドカード型だけは具象化可能で、List<?>[]は許されています。

このあとの段落ではジェネリック型の配列が禁止されているために起きる影響が書かれています。まず、ジェネリック型の配列を返すことはできません。

つぎに挙げているのは、項目24の無検査ジェネリック配列生成警告の例で挙げたような可変長引数とジェネリック型を組み合わせたパターンです。

そして最後に、ジェネリック配列生成エラーが出る場合の最善の解決策は、配列型E[]よりコレクション型List<E>を使用することと結んでいます。その例を残りの2ページ全部使って説明しています。最初の前置きが難しいですが、本書を読みながらサンプルコードを実際に書いていくと、言っていることはなるほどわかりやすいとなると思います。

項目25は以上です。

広告
  • LINEで送る