Effective Javaを読むチャレンジ-項目28その2-

ワイルドカード型を適用する

項目28の後半はPECS原則をもとにパラメータ化された型を使用しているジェネリックメソッドに対してワイルドカードをどのように適用していくのか、ということを今までのジェネリックスの章で出てきたサンプルコードを使って説明しています。

最初のサンプルは項目25に出てきた(本サイトでは省略しちゃいましたが)リストを簡約するreduceメソッドです。

このメソッドを使ってリストの数値をすべて乗算する処理を書きたいと思います。このとき、Functionは汎用的にFunction<Number>としておき、今回は整数の乗算を実装しようとしてList<Integer>を使用するとします。するとクライアントのコードはこうなります。

ところがこのコードはコンパイルされません。理由はreduceメソッドの第1引数がList<Number>しか許容しないからです。ということでこのreduceメソッドに柔軟性をもたせるべくreduceメソッドの引数を適切なパラメータ型にしたいと思います。

PECS原則に従って、List<E>の型パラメータEがプロデューサーかコンシューマーかを調べます。このEは「snapshot = new ArrayList<>(list);」の部分でプロデューサーとして使用します。ArrayList(Collection)コンストラクタは、サイズのあるリストが引数として渡された場合にそのコレクションのコピーを作成します。つまりこのEはコピー先であるsnapshotを生産するためだけに使用されています。それが終われば以降のコードでこのEは使用されません。

という理由で、reduceメソッドの第1引数のパラメータ化された型は「<? extends E>」という境界ワイルドカードに変更できます。

次に第2引数のEはどうでしょうか。このEはFunctionインタフェースで定義された関数で使用されますが、その関数がEを生産に使用するのか消費に使用するのかは分かりません。そこはAPIのクライアント側に任せているからです。こういった生産も消費もありうるEに対してワイルドカードを使用するのは適切ではありません。よって第2引数はパラメータ化された型のままとします。

次に第3引数ですが、これもFunctionの関数に渡されるため、第2引数と同様にパラメータ化された型のままとします。このように修正すれば上記のクライアントコードはコンパイルされ、実行結果も期待どおりになります。

次のサンプルは項目27に出てきたunionメソッドです。

このメソッドも柔軟性を持たせるべくワイルドカードの適用を検討します。まずは第1引数のEは「result = new HashSet<E>(s1);」の部分で生産のために使用されていますので「<? extends E>」とします。

第2引数は「result.addAll(s2);」の部分でやはり生産のために使用されていますので「<? extends E>」となります。

あとワイルドカードにできるのは戻り値の型である「Set<E>」のEですが、本書では戻り値の型としてワイルドカードを使用しないでくださいとあります。それをちょっと確かめます。unionメソッドに対して下記のようにワイルドカードを適用したとします。

そしてクライアントコードを次のように書きます。

上記のコードは「Set<capture#1-of ? extends Integer> から Set<Number> には変換できません」というコンパイルエラーが出ます。このコードを修正するには「Set<? extends Number> unionSet = …」とするか「Set<?> unionSet = …」とすればいいのですが、以前の項目に出たとおり、ワイルドカードを使用したインスタンスは参照およびnullの挿入しかできません。このように、戻り値の型にワイルドカードを使用すると、クライアントにワイルドカードの使用を強制するのでダメだよと本書では言っているのですね。この場合のメソッド宣言は以下のようにするのが適切です。

で、この後の話が謎でして、本書ではこのメソッド宣言を見ると次のコードを書きたくなって、それを実行するとエラーが出ると言っています。

ところが、項目27で紹介されていたunionメソッドのテストプログラムを流用すると本書のようなエラーは出なくて正常にコンパイル実行されます(Java SE 8)。コードが不適切だったのか不安を抱えつつもとりあえず読み飛ばします。

しかし読み飛ばしてすぐに、そのエラーを解消するために明示的型パラメータというものを使ってどの型を使用するかをコンパイラへ明示的に指示する方法があると言われます。どんなエラーかよく分かっていないので、この明示的型パラメータを使うとエラー解消につながる理由が分かりません。

ここで止まってしまいました。このサンプルと明示的型パラメータについて調べ直す必要がありますね・・・Java SE 7以降に型推論が改善された可能性が高そうですが・・・今回は以上です。


2016.4.15追記

Java SE 8以降ではコンパイルされるようです。Java SE 7以前では本書のような型変換エラーが発生し、明示的型パラメータを使用することでそのエラーが解消されました。いったい何が変わったのでしょうかね・・・。

広告
  • LINEで送る