LoginSignup
43

More than 3 years have passed since last update.

[Java] ジェネリクス 境界ワイルドカード型のご紹介

Last updated at Posted at 2019-09-11

境界ワイルドカード型の役割

Javaのジェネリクス型は、List<Integer> 型を List<Number>型に変換することは出来ません。

型が異なる
// OK 型パラメーターが同一の場合
List<Integer> foo = new ArrayList<Integer>();

// NG List<Integer> は、List<Number> に変換できない
List<Number> bar = foo;

// NG 初期化でも同様に変換不可
List<Number> foo = new ArrayList<Integer>();

そこで境界ワイルドカード型を使うことで、型パラメーターをある程度幅を持たせて宣言する ことが出来ます。
しかしながら境界ワイルドカード型には2種類存在します。
これは型の境界を定めた時に、それぞれある場面において処理系が「型を定められない」という問題が発生するためです。
ひとつひとつその問題を見ていこうと思います。

解説には以下のFruitクラスを用います。

Fruitクラス.png

上限付きワイルドカード型

Generic<? extends T>

型パラメーターを上記のように指定するとその型パラメーターは 上限付きワイルドカード型 です。
? extends T 型は、Tもしくはそのサブクラスを全て表す型 を意味します。

?_extends_fruit.png

List<? extends Fruit> 型を使って考えると、Fruitクラスもしくはそのサブクラスを指定したList型 から変換が可能です。

変換可能なList一覧
List<? extends Fruit> basket = new ArrayList<Fruit>(); // 1
List<? extends Fruit> basket = new ArrayList<Melon>(); // 2
List<? extends Fruit> basket = new ArrayList<Lemon>(); // 3

次に、List<? extends Fruit> basket 変数から要素を取り出すことを考えますが、
ここでbasket変数は、1のときも2のときも3のときも、要素をFruitクラスとして取得が可能なことを認識しておいてください。

読み込み

「上限付き境界ワイルドカード型」から「Tクラス型」への変換

List<? extends Fruit> basket 変数から要素を取り出すことを考えます。
上限付きワイルドカード型の場合、 少なくともTクラスとそのサブクラスを表す型 であるから、Listから要素を取得する際、その上限であるTを指定することが可能です。

上限付きワイルドカード型をT型へ変換
List<? extends Fruit> fruits = ...

// 問題なし
Fruit fruit = fruits.get(i);

これは ? extends T 型から T 型への変換になっています。

「上限付き境界ワイルドカード型」から「Tのサブクラス型」への変換

次にbasket変数から要素を取得する際に、そのサブクラスを指定出来るかを考えます。
List<? extends Fruit> 型には少なくとも以下の3つのList型から変換可能です。

  1. ArrayList<Fruit>
  2. ArrayList<Melon>
  3. ArrayList<Lemon>

従って要素を取得する際に、いくつもの系列に派生していくサブクラスを指定することが出来ません。

上限付きワイルドカード型はサブクラス型に変換できない
List<? extends Fruit> basket = ...

// NG コンパイルエラー Melon型には変換できません
Melon fruit = basket.get(i);

上記、もし? extends Fruit 型をサブクラスへと変換出来るとした場合、以下の矛盾が発生する。

上限付きワイルドカード型をTのサブクラス型へ変換1
// メロンリストを作る
List<Melon> melonList = new ArrayList<Melon>()
melonList.add(new Melon());

// List<Melon> melonList型をList<? extends Fruit>型に変換
List<? extends Fruit> basket = melonList;

// サブクラスへの変換が可能であればLemonとして受け取ることが可能となってしまう
Lemon fruit = basket.get(i);

従ってサブクラスへの変換が許されていません。
よって ? extends T 型から Tのサブクラス 型への変換は不可です。

書き込み

次に List<? extends Fruit> fruits 変数への要素の追加を考えます。
? extends Fruit 型 から Fruit型 への変換は出来るので、以下のfruits変数にFruitオブジェクトを追加出来るように思われます。

上限付きワイルドカード型への書き込み
List<? extends Fruit> fruits = new ArrayList<Fruit>();

// 一見可能に思える
fruits.add(new Fruit());

しかしながら上記を許してしまうと、以下の矛盾が発生します。

上限付きワイルドカード型への書き込みは矛盾が発生する
// fruitsの実態はメロンリスト
List<? extends Fruit> fruits = new ArrayList<Melon>();
// LemonクラスはFruitクラスのサブクラス
Fruit fruit = new Lemon();

// メロンリストにレモンが追加出来ることになる
fruits.add(fruit);

従って、具体型であるT 型もしくは Tのサブクラス 型から? extends T 型 への変換は出来ません。
これはList型を上限付きワイルドカードで宣言すると、要素を追加することが出来ないことを意味します。
そこでもう一つの境界ワイルドカード型の出番です。

※ Appendixに具体型から変換するということについて解説を追記しています。

下限付きワイルドカード型

Generic<? super T>

型パラメーターを上記のように指定するとその型パラメーターは、下限付きワイルドカード型 です。
? super T 型は、Tもしくはそのスーパークラス全てを表す型 を意味します。

?_super_melon.png

List<? super Melon> 型には、Fruitクラスもしくはそのスーパークラスを型パラメーターとするList型 から変換出来ます。

変換可能なList一覧
List<? super Melon> baskets = new ArrayList<Melon>(); // 1
List<? super Melon> baskets = new ArrayList<Fruit>(); // 2
List<? super Melon> baskets = new ArrayList<Food>(); // 3
List<? super Melon> baskets = new ArrayList<Object>(); // 4

今度は前後して、basket変数へ要素を追加することを先に考えますが、
1~4において、1も2も3も4のときもbasket変数には、Melonクラスが追加可能であることを認識しておいてください。

書き込み

下限境界ワイルドカード型への変換

List<? super Melon> basket 変数へ要素を追加するには、下限付きワイルドカード型の場合、少なくともTクラスもしくはそれ以上のスーパークラスを表す型 であるから、List型へ要素を追加する際、その下限であるTを指定することが出来るはずです。

例を参考に一つ一つ追加出来ることを確認します。

List<? super Melon> fruits = new ArrayList<Object>() の時

Objectリストへの追加
// OK
fruits.add(new Melon())
// OK
fruits.add(new WaterMelon());

List<? super Melon> fruits = new ArrayList<Food>() の時

Foodリストへの追加
// OK
fruits.add(new Melon())
// OK
fruits.add(new WaterMelon());

List<? super Melon> fruits = new ArrayList<Melon>() の時

Melonリストへの追加
// OK
fruits.add(new Melon());
// OK
fruits.add(new WaterMelon());

つまり T もしくは Tのサブクラス 型から ? super T 型への変換は可能です。

読み込み

? super Melon 型は、「Melonクラスとそのスーパークラスを全て表す型」です。
またJavaのすべてのクラスは、Object 型を継承しています。
従って ? super T 型から Object 型への変換は可能です。

下限付きワイルドカード型からObject型への変換
Object object = basket.get(i)

まとめ

  • 境界パラメーター型を用いればジェネリクス型の型を幅広く表すことが出来ます
  • 上限付き境界ワイルドカード型と具体型の変換表

    可否 変換元 変換先 その意味
    OK ? extends T T T型の値の取得が可能
    NG T ? extends T 書き込みが不可
  • 下限付き境界ワイルドカード型と具体型の変換表

    可否 変換元 変換先 その意味
    OK ? super T Object Object型として値の取得が可能
    OK T ? super T T型の値の書き込みが可能
    OK Tサブクラス ? super T Tのサブクラス型の値の書き込みが可能
  • 一般的に PECS という呪文が存在し、上限付き境界ワイルドカード型をProducerと呼び、下限付き境界ワイルドカード型をConsumerと呼ぶことがあります

    • Producer - 値の生成専門
    • Consumer - 値の受取専門

Appendix

境界ワイルドカード型による値の書き込みが意味すること

上限付き境界ワイルドカード型を指定した時、そのListは書き込むことが出来ないということを耳にしたことがあるかもしれません。

これは単純に以下が出来ないという意味です。

具体型 から 上限付きワイルドカード型 への変換が出来ない

ジェネリクス型オブジェクトを上限付きワイルドカードを用いて宣言するとその型パラメーターを持つ関数を呼び出した際に上記の変換になってしまいます。

以下Stack<T> クラスを考えます。

Stackクラス
class Stack<T> {
    void push(T e) {}
}

Stack<T>クラスを上限付きワイルドカード型を用いて宣言します。

Stack<? extends Fruit> basket = new Stack<Fruit>();

この時basket変数の型パラメーターTは ? extends Fruit 型です。
つまりpushメソッドは以下と同等です。

Stackの型パラメーターが上限付きワイルドカードのとき
void push(`? extends Fruit` e) {}

従ってこのpushメソッドを呼び出すと、具体型(Fruit) から ? extends Fruit 型への変換になります。
そしてこの変換はNGです。

// NG Fruit型を `? extends Fruit` 型へは変換できない
basket.push(new Fruit()); 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43