The HIRO Says

If you smell what The HIRO is cooking!!!

【Generics】型パラメータの拡張(1)−extends

型パラメータ(<E>とか)には、<? extends E>という書き方もあります。
今回は、この extends について説明します。

例:Stack クラス

ひとまず、下記のような API の Stack クラスを想定して下さい。
(各メソッドの実装は、説明に影響がないので省略しています。)

public class Stack {

    // stack に要素を追加する。
    public void push(E e); {
    }

    // stack から要素を取り出す。
    public E pop() {
    }

    // stack が空の場合、true を返す。
    public boolean isEmpty() {
    }
}


ここに、複数の要素を一度に push できる pushAll() というメソッドを定義するとします。
こんな感じ。

    // 指定された要素を全て stack に追加する。
    public void pushAll(Iterable src) {
        for (E e : src) {
            this.push(e);
        }
    }

※Iterable は、List とかの親インターフェースで、Iterator を返す iterator() を定義しています。
要は、List とかは Iterable ということです。

問題点

以下のプログラムを実行すると、pushAll() でコンパイルエラーになります。

    Stack target = new Stack();

    List list = new ArrayList();
    list.add(12);
    list.add(34);
    list.add(56);

    target.pushAll((Iterable) list);

これは、Stack と pushAll() の Iterable の型パラメータがぴったり一致していないためです。
(Number と Integer なので、ぴったり一致ではない。)


でも、Number は Integer の親クラス。
使えてもいいのでは?と思うのが人情。
何かいい方法はないか???

解決策

ここで extends を使うとうまく行きます。

    public void pushAll(Iterable<? extends E> src) {

追記

混乱するかもしれませんが、下記のコードは正常に動作します。

    Stack target = new Stack();
    target.push(Integer.valueOf(123));

【理由】

  1. Stack と push(E e) と、定義上型パラメータが一致している。(pushAll() の場合は Iterable
  2. Number は Integer の親クラス。

定義上の型パラメータが一致する場合としない場合とで、対応方法が違うということです。

指針

今回の pushAll のように、型パラメータがクラス定義と一致しない場合かつ、引数の値をプログラム内部で取り出す処理を行う場合、extends と定義するとうまく行きます。


課題

型パラメータには、<? super E> という書き方もあります。
それについては、後日説明します。