LoginSignup
1
2

More than 5 years have passed since last update.

Optional#orElseの欠陥

Last updated at Posted at 2018-11-09

Qiitaにコメントを書いていて気づいたことを書き留めておく

Listの場合

上限境界ワイルドカードList
List<Integer> list = new ArrayList<>();
list.add(1);
List<? extends Number> list2 = list;

を考える。この段階でlit2が扱うのは「Numberを継承した何か」であって、それがIntegerなのかDoubleなのかわからない。だから

上限境界ワイルドカードListに追加はできない
list2.add(0.5);  // コンパイルエラー

これはコンパイルエラーとなる。現に今list2に入っているのはIntegerのListで、Doubleを入れることはできないし、できたら型安全が崩れる。
しかし一方で、「Numberを継承した何かが入っている」のは確かなので、

上限境界ワイルドカードListから取得はできる
Number num = list2.get(0);

とすることはできる。実際入っているものがIntegerだろうがDoubleだろうが、Numberにアップキャストが可能だからだ。

本題:Optionalの場合

上限境界ワイルドカードOptional
Optional<? extends Number> op = Optional.of(1);

を考える。getは(あまり推奨されないが)Listのgetと同じ理由で、Numberとして取り出すことができる。

上限境界ワイルドカードOptionalから取得できる
Number num = op.get();

ではorElseはどうか?orElseはOptionalが空だった場合に返すデフォルト値を渡す必要がある。
getではNumberを取得できるのだから、Numberを渡せば…

上限境界ワイルドカードOptionalでorElseが使えない
Number num2 = op.orElse(0.5);  // コンパイルエラー

…あれ?

そう、理屈としてはList#addと同じ。引数型Tに当たる型が「Numberを継承した何か」であり、それがIntegerかDoubleかわからないため、確実に安全に入れられる型が存在しないのだ。

…おかしくね?
そもそもList#addとOptional#orElseでは引数の性質が全く異なるはずである。
Listのほうはある型についての要素をまとめるもので、異なる要素が紛れ込むのは避ける必要がある。
しかし、Optionalは中身が空だった場合に代わりに使うものであり、Optionalの中に入れたりすることが無いため、異なる型がまぎれたりする恐れはない。そもそも扱う型の親の型にならなんにでも入れられるはずなのに、そこの柔軟性がないのはおかしい。つまり、Optional#orElseの定義は

Optional#orElseのあるべき定義
public <U super T> U orElse(U other)

こうでなければならないはずだ。

しかし今やってみたら、ワイルドカードでない総称型にsuperは使えないらしい…
これをやろうと思ったら、いったんmapで型を変えないといけないのか…

Optionalの型の親の型を返すためにいったん変換
Number num3 = op.map(n -> (Number) n).orElse(0.5);
1
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2