Qiitaにコメントを書いていて気づいたことを書き留めておく
Listの場合
List<Integer> list = new ArrayList<>();
list.add(1);
List<? extends Number> list2 = list;
を考える。この段階でlit2が扱うのは「Numberを継承した何か」であって、それがIntegerなのかDoubleなのかわからない。だから
list2.add(0.5); // コンパイルエラー
これはコンパイルエラーとなる。現に今list2に入っているのはIntegerのListで、Doubleを入れることはできないし、できたら型安全が崩れる。
しかし一方で、「Numberを継承した何かが入っている」のは確かなので、
Number num = list2.get(0);
とすることはできる。実際入っているものがIntegerだろうがDoubleだろうが、Numberにアップキャストが可能だからだ。
本題:Optionalの場合
Optional<? extends Number> op = Optional.of(1);
を考える。getは(あまり推奨されないが)Listのgetと同じ理由で、Numberとして取り出すことができる。
Number num = op.get();
ではorElseはどうか?orElseはOptionalが空だった場合に返すデフォルト値を渡す必要がある。
getではNumberを取得できるのだから、Numberを渡せば…
Number num2 = op.orElse(0.5); // コンパイルエラー
…あれ?
そう、理屈としてはList#addと同じ。引数型Tに当たる型が「Numberを継承した何か」であり、それがIntegerかDoubleかわからないため、確実に安全に入れられる型が存在しないのだ。
…おかしくね?
そもそもList#addとOptional#orElseでは引数の性質が全く異なるはずである。
Listのほうはある型についての要素をまとめるもので、異なる要素が紛れ込むのは避ける必要がある。
しかし、Optionalは中身が空だった場合に代わりに使うものであり、Optionalの中に入れたりすることが無いため、異なる型がまぎれたりする恐れはない。そもそも扱う型の親の型にならなんにでも入れられるはずなのに、そこの柔軟性がないのはおかしい。つまり、Optional#orElseの定義は
public <U super T> U orElse(U other)
こうでなければならないはずだ。
しかし今やってみたら、ワイルドカードでない総称型にsuper
は使えないらしい…
これをやろうと思ったら、いったんmapで型を変えないといけないのか…
Number num3 = op.map(n -> (Number) n).orElse(0.5);