Effective Javaを読んでいる。Javaでは、配列は使わずにリスト(ジェネリクス)を使えということだったのでメモ。
ジェネリクスとは
ジェネリクスとは、「総称性(Genericity)」「ジェネリック・プログラミング」とも呼ばれるプログラミング技法で、 オブジェクト指向とは異なるパラダイムからきたものである。データの型に束縛されず、型そのものをパラメータ化して扱うことができる。Javaでは主にコレクションクラスに導入されている。
変性とは
何故、配列よりリストを使うことが推奨されるのかを理解するために、先に変性について理解しておく必要がある。
- 共変(covariant):狭い型(String)から広い型(Object)へ変換すること。
- 反変(contravariant):広い型(Object)から狭い型(String)へ変換すること。
- 不変(invariant):型を変換できないこと。
ここでの「広い」や「狭い」とは、機能が「広い」や「狭い」という意味である。全てのクラス型は暗黙的にObject型を継承して作られている。つまり、Object型は全てのクラス型のスーパータイプ(親の型、親のクラス)ということである。
String型(Objectのサブタイプ) ⇒ Object型(Stringのスーパータイプ) の変換は、共変(covariant)である。
Object型(Stringのスーパータイプ) ⇒ String型(Objectのサブタイプ) の変換は、反変 (contravariant)である。
型の変換ができないのは、不変 (invariant)である。
配列よりリストを選ぶ
Javaの配列とリスト(ジェネリクス型)の最も大きな違いは、次の点である。
- 配列は共変である。
- リスト(ジェネリクス型)は不変である。
以下より、それぞれの点について詳しく説明する。
配列は共変
配列は共変という性質を持っているので、Object[]にString[]を代入することが出来る。つまり、String[]はObject[]のサブクラスである。
String[] strArray = {"test1", "test2"};
Object[] objArray = strArray; // 配列は共変なので代入可能
objArray[0] = new Integer(3); // java.lang.ArrayStoreException
リスト(ジェネリクス型)は不変
一方の、リスト(ジェネリクス型)は不変なので、Object[]にString[]を代入することが出来ない。
List<String> strList = new ArrayList<String>();
strList.add("test1");
strList.add("test2");
List<Object> objList = new ArrayList<Object>();
objList = strList; // コンパイルエラー
配列やリストでは、型の混入は避けたい現象である。配列の場合は、実行時に不正な型が混入した場所で例外(java.lang.ArrayStoreException)を投げてくれる。一方、リスト(ジェネリクス型)では、コンパイル時にエラーとなる。実行時ではなく、コンパイル時にきちんと例外を投げてくれると、バグの混入場所が特定が容易である。
上記を踏まえて、Javaでは配列ではなく、リスト(ジェネリクス型)を使用することを推奨する。
参考
書籍 Effective Java
共変性と反変性 (計算機科学) – Wikipedia
今まで知らなかった 5 つの事項: Java コレクション API の場合: 第 1 回
なぜ Java の配列は共変で、Generics は共変ではないのか – sinsengumi血風録