[Java] ジェネリックメソッドの戻り値は境界ワイルドカード型にしない

2020年2月10日月曜日

Java ジェネリクス

前回に続き、Effective Javaネタ。

PECS

Effective Java 項目31 APIの柔軟性向上のために境界ワイルドカードを使う によると"最大限の柔軟性のためには、プロデューサ(生産者)かコンシューマ(消費者)を表す入力パラメータに対してワイルドカード型を使ってください。"とある。
確かにメソッドの引数に関してはその通りだと思う。 しかし、メソッド使用者の利便性を考えると、"戻り値型として境界ワイルドカード型を使わないでください。"とある。こちらについてはどうだろうか。

戻り値としてリストが返ってくるメソッド

例えば、リストを生成するメソッド(まさにProducer!)List<T> func<T>(T a)を考えよう。
List<Integer> li = func(1);

funcはリストを作成するメソッドなので、戻り値をより一般的にしようとして、List<? super T> func<T>(T a)とすると困ったことが起こる。
// コンパイルエラー!
List<Integer> li = func(1);

ジェネリック型同士の汎化・特化関係

...と、ここで、これはPECS原則と代入の方向を勘違いしていることが原因だと気づいた。そもそも、戻り値をどう使うかはメソッド使用者側の問題なので、メソッドのデザインとして、PECSの原則には当てはまらない。

ワイルドカード型のジェネリック変数にワイルドカードでないジェネリック型を代入することはできるけど、ワイルドカードでないジェネリック型の変数にワイルドカード型を代入することはできない。

というか、代入先のワイルドカードがより広い範囲を指しており、範囲に矛盾がなければ代入が可能ということになる。
List<Integer> li = new ...;
// ? extends Number は Integer よりも広い.
List<? extends Number> len = li;
// ? は ? extends Number よりも広い.
List<?> lq = len;

List<Number> ln = new ...;
// ? super Integer は Number よりも広い.
List<? super Integer> lsi = ln;

だから、イメージとしてはワイルドカード型は非ワイルドカード型を汎化したもので、非ワイルドカード型はワイルドカード型を特化したものと考えるといいのかもしれない。

ワイルドカード型同士も、より狭い範囲を指している型ほど特化している。より広い範囲を指している型ほど汎化しているということになる。
List<? extends Number> len = ...;
// ? extends Object は ? extends Number よりも広い.
List<? extends Object> leo = len;