Javaでは配列よりリスト(ジェネリクス)を選ぶ

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血風録