ジェネリック 境界ワイルドカード型の整理
境界ワイルドカードは他のプログラミング言語には多分存在しない。 ただこれがあることで、メソッド間でデータのやり取りが楽になる。
役割
Javaのジェネリックは、型パラメーターが継承関係であっても異なる型になる。
つまり List<Number>
と List<Integer>
は型が異なり、メソッドの引数に対して指定することができない。
(Swiftでは問題なく渡すことが出来る)
void receiveNumbers(List<Integer> Integers) { .. }; List<Number> integers = new ArrayList<Integer>(); // 型が異なるので渡せない reciveNumbers(integers);
そこで 型パラメーターをある程度幅のある指定が出来るようにした のが、境界ワイルドカード
(従って型制約とは全然違うことに注意)
// 「? は Integerのsuperクラスである」と読める void receiveNumbers(List<? super Integer> numbers) { .. }; List<Number> integers = new ArrayList<Integer>(); // Number は Integerのスーパークラスなので渡すことが可能 reciveNumbers(integers);
種類
境界ワイルドカード型は2種類存在する
- Producerタイプ - 値の取得のみ可能
- Comsumertタイプ - 値の書き込みが可能、取得はObject型としてのみ。
以下のFruitクラスを例に整理する
Fruit型のポイントは以下
- 全JavaのクラスはObject型を継承していること
- Fruit型は2系統あること
- Melon 系列
- Lemon 系列
Producerタイプ Generics<? extends T>
? extends Fruit
は、以下の4つの型を表せる
Fruit
Melon
WaterMelon
Lemon
fruits への代入
1. Fruit<? extends Fruit> fruits = new ArrayList<Fruit>(); 2. Fruit<? extends Fruit> fruits = new ArrayList<Melon>(); 3. Fruit<? extends Fruit> fruits = new ArrayList<WaterLemon>(); 4. Fruit<? extends Fruit> fruits = new ArrayList<Lemon>();
ワイルドカードは Fruit クラスを継承していることだけが確定しているが、Melon
であるのか Lemon
であるのかサブクラスは特定できない。
従って、Fruit
型として取得することが可能であるが、直接サブクラスを指定することはできない。
// OK Fruit fruit = fruits.get(i); // NG fruits = new ArrayList<Lemon>(); の可能性があるため矛盾する Melon melon = fruits.get(i); // NG fruits = new ArrayList<Melon>();の可能性があるため矛盾する Lemon lemon = fruits.get(i);
Producerへ書き込めない理由
List<? extends Fruit> fruits
一見、Fruit型オブジェクトは書き込むことができそうだが、実際の要素の型は確定できないため、以下の矛盾が発生する。
// fruits は Melonのリスト Fruit<? extends Fruit> fruits = new ArrayList<Melon>(); // LemonクラスはFruitのサブクラス Fruit lemon = new Lemon(); // もしProducerへの書き込みができたならば、Lemonインスタンスを追加できてしまう fruits.add(lemon);
従ってfruitsのサブタイプのクラスが確定しないため、Producerには書き込むことができない。
Consumerタイプ Generic<? super T>
fruits への代入
1. fruits = new ArrayList<Melon>(); 2. fruits = new ArrayList<Fruit>();
ワイルドカードは、Melonのスーパークラスであることは確定しているので、Melon系列の 要素を書き込むことが可能
fruits.add(new Melon()); // OK fruits.add(new WaterMelon()); // OK
一見、
2. fruits = new ArrayList<Fruit>();
からfruitsには Fruitオブジェクトを書き込めるように思えるが、以下のように矛盾が発生する。
// fruits の要素は Melonクラスを祖先とするクラスである Fruit<? super Melon> fruits = new ArrayList<Fruit>(); // LemonはFruitのサブクラス Fruit fruit = new Lemon(); // Fruitが追加できるようにすると矛盾が発生する fruits.add(fruit);
従って書き込めるのはMelonクラスを継承しているオブジェクトのみとなる。
Object型としか読み込めない理由
Fruit<? super Melon> fruits
fruits の要素の型は Melonクラスの先祖のどれかということになるので、唯一 Object型であることのみ確定している。 従ってObject型としては読み込むことが可能
Object object = fruits.get(i); // OK Melon melon = fruits.get(i); // NG FruitクラスのサブクラスであるLemonかもしれない