Java Generics Hell - 反変ワイルドカード

Java Generics Hell アドベントカレンダー 14日目。

読者の推奨スキルとしてはOCJP Silverぐらいを想定している。

反変ワイルドカード

さて、前回は共変ワイルドカードについてだった。今回は反変ワイルドカードについてである。

反変ワイルドカードは? superを用いて以下のように記述する。

List<A> listA = new ArrayList<A>();
List<? super B> listSuB = listA; // 代入可能

このlistSuBは次のような性質を持っている。

  • listSuB は B型を格納することが出来る
  • listSuB から取り出した型はObject型となる

前回のList<? extends A>型は、戻り値がA型であることが保証された。代わりに引数でA型オブジェクトを渡すことができなくなった。

List<? super B>型の場合は逆で、B側を引数として渡すことを保証する代わりに戻り値の型が保証できなくなるのだ。B型の親であるA型であることも保証できない。全ての型の始祖であるjava.lang.Object型としてだけget()することが可能となっている。

反変ワイルドカードの意義

メソッド内部では、共変ワイルドカードで渡されたオブジェクトから値を取ることが出来るのであった。対して、反変ワイルドカードで渡されたオブジェクトには値を渡すことが出来るのである。

考え方としてはサプライヤー、つまり、そのオブジェクトから何か値が供給される場合、? extends を用いる。対してコンシューマー、そのオブジェクトに値を渡して消費させる場合は? superを用いると良い。

事例

ワイルドカードの宝庫であるStreamAPIからjava.util.function.FunctionクラスのandThenを例に挙げよう。

以下ではclass A, B, C はAが親、Bが子、Cが孫である。またclass X, Y, Z ではXが親、Yが子、Zが孫である。

まずFunction<T,R>のインスタンス型変数はTが入力の型(汎用的にTypeの頭文字のTだろう)、Rが結果の型(これはResultの頭文字Rだろう)となっている。ここで、T型を受け取り、B型を返すFunction<T, B>を考えよう。

ここでandThenのメソッドシグニチャ

public <V> Function<T,V> andThen(Function<? super R,? extends V> after) { ... }

であり、V型はメソッドスコープの型変数である。ここでは簡単のためV型はY型としよう。
そしてFunction<T, B>であるので、

public Function<T,Y> andThen(Function<? super B,? extends Y> after) { ... }

と考えて良い。T型を受け取り、B型を返すFunction<T, B>に、B型を受け取りY型を返すFunction<B, Y>を与えると、くっつけてT型を受け取りY型を返すFunction<T, Y>にすることができる、というわけだ。

ここでandThenの実装コードを見てみよう。

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

RにBを、VにYをバインドして、実際の型のイメージで書き下ろすと以下のようになる。

default Function<T, Y> andThen(Function<? super B, ? extends Y> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

ラムダ式で書かれているが、T型の引数tを受け取ると、Function<T, B>のにtを渡してapply(t)の結果Bをさらにafterのapplyに渡す。今、Function<T, B>であるから、afterはB型を受け取って処理できる必要がある。

しかし、afterはB型だけではなく、親のA型を受け取ることが出来ても良い。しかし、孫のCしか受け取れないようではB型が渡されてくると困る。これが? super Bの効果。

そして、afterはY型を返さなくてはならない。しかし、実はZ型だけを返す実装でも良い。しかし、Yの親のXを返しては困る。これが? extends Yの効果。

このように、andThen(Function<B, Y> after)に対してandThen(Function<? super B, ? extends Y> after)としたほうが、afterの実装がより広く取ることができる。