Javaジェネリクス入門、その7

2019年7月24日

Javaジェネリクス入門、その6の続きである。JavaジェネリクスについてはJavaジェネリクスにまとめがあるので参照されたい。

前回は共変を説明した。今回は反変だ、もし完全に理解できる自信があるならば、この回は読まずにJavaジェネリクス:共変、反変、非変(これ以上簡単にはならない)を読んで欲しい。

共変のまとめ

反変の前に共変をまとめてみる。

共変オブジェクトからは値の取得のみが可能であり、書き込むことはできない。

List<? extends Animal>animalList = ...
Animal animal = animalList.get(0); // 取得は可能
animalList.add(new Cat()); // 書き込みはだめ

上の場合の「? extends Animal」という「共変」化したリストの目的としては、「Animal以下の要素を持つリストから値を取得して何かする」ためのものだった。書き込みは禁止されている。

反変とは?

共変とは逆に「取得はできないが、書き込みはできる」オブジェクトを作成するものが「反変」だが、想像の通り共変に比較すると出番はずっと少なくなる。実際にどのような場面で使うのか具体例を上げるのも難しくなる。

例えば、動物リストに犬や人間を格納することを考えてみる。普通に考えれば以下だ。

List<Animal>animalList = new ArrayList<>();

// いろいろな動物を格納する
animalList.add(new Dog());
animalList.add(new Cat());

この動物格納部分をメソッドにし、引数として格納先リストを与えたいものとする。

void addAnimals(List<Animal>animalList) {
  animalList.add(new Dog());
  animalList.add(new Cat());
}

この引数としては、List<Animal>しか受け入れられないのだが、しかし、AnimalはObjectでもあるで、List<Object>も使えるようにしたい。しかし、このままだとエラーになってしまう。

addAnimals(new ArrayList<Object>()); // エラー

このような時に反変として定義する。

void addAnimals(List<? super Animal>animalList) {
  animalList.add(new Dog());
  animalList.add(new Cat());
}

「? super Animal」は、「Animalか、その上位クラス」を意味する。Animalだけではなく、その上位のクラスでも良いということだ。これでリストの要素はAnimalでも、その上位のObjectでもOKになる。

addAnimals(new ArrayList<Object>()); // OK
addAnimals(new ArrayList<Animal>()); // OK

反変の性質

さて、反変のリストオブジェクトを引数とするメソッド内では、どのような処理が許されるかだが、基本的には書き込みのみが許され、読み込みは許されなくなる。

これらの操作制限は、その意味を考えてみれば理解できるだろう。

// リストの要素はAnimalかその上位のクラスである
void addAnimals(List<? super Animal>animalList) {

  // 要素型はAnimalの可能性があるので、Animal以下しか追加できない
  animalList.add(new Dog()); // これはOK
  animalList.add(new Cat()); // これもOK
  animalList.add(new Animal()); // これもOK
  animalList.add(new Object()); // これはダメ。Animalではない。

  // 要素型はObjectの可能性があるので、取得できるのはObjectとしてのみ
  Animal a = animalList.get(0); // ダメ。Objectの可能性がある。
  Object b = animalList.get(0); // これはOK
}

共変とは逆で、基本的に書き込みは許されるが、意図するような読み込みはできなくなる。

共変・反変の図解

図で示すと以下のようなところだ。

続きはJavaジェネリクス入門、その8になる。