Java の Optional に関する記事を見ると “この教え方では Optional の意味がまるっきりない使い方するやつばかりが生まれるぞ……” というのを数多く見てしまったので,せめてもの反抗にこの記事を書きます。
TL; DR
isPresent()
とget()
しか知らない奴は
こういう書き方をしてはいけない
記事を見ていると
null チェックは
Optional#isPresent()
で行い,値はOptional#get()
で取り出します。
みたいな解説が多すぎる。
そういうことをしてはいけない。それでは従来の null チェックとなにも変わらない。変わらないどころかタイプ数が増え,実行時間が増え,メモリ使用量が増え**,デメリットだけが上乗せされている**。メリットはなにも得られていないのに。
Optional<String> someStr = Optional.of("Hello, world!");
if (someStr.isPresent()) {
System.out.println(someStr.get()); // やってはいけない
}
まず頭に叩き込んでほしいのは**Optional#get()
を使ってはいけない**ということだ。
大事なことなのでもう一度言う。
Optional#get()
は使うな。
Optional#get()
はそれが null かどうかを気にせずに取り出すウンコみたいなメソッドだ。Unsafe なのだ。“どーしてもそれを使わないといけないとき” のためだけに用意されている。
“直前に null チェックしてるからいいじゃん” とか抜かす奴は Antony Hoare にぶち怒られてからこの記事に戻ってくるように。“null チェックしてるからいい” なら従来の方法で十分なのだ。無意味だ。お前が null チェックを忘れようがOptional#get()
は使える。そしてクラッシュする。バカバカしい話だ。
ではどうするか。Optional#ifPresent()
を使う。このメソッドはメソッド参照を引数に取る。ので,ラムダ式を中に記述していくが……ラムダ式もメソッド参照も知らなくてもそれっぽく書けばいい。
Optional<String> someStr = Optional.of("Hello, world!");
someStr.ifPresent(x -> {
System.out.println(x);
});
Optional#ifPresent()
に渡されたメソッド参照は null でないときだけ実行される。x
は一時的に使える変数だと思っていい。もちろん名前は (かぶらない限りは) 自由だ。型は当然Optional<T>
に対してT
となる。ラムダ式やメソッド参照について知っておくのが一番だが,知らなくても通常の if 文と見た目は似ているので十分のはずだ。
null でないときだけ実行したいのではなく,値の有無で動作を分けたい場合もあるかもしれない。
Optional<String> someStr = Optional.empty();
if (someStr.isPresent()) {
System.out.println(someStr.get()); // もちろんやってはいけない
} else {
System.out.println("値はなかったよ");
}
そういうときは**Optional#ifPresentOrElse()
を使用する**。
Optional<String> someStr = Optional.empty();
someStr.ifPresentOrElse(x -> {
System.out.println(x);
}, () -> {
System.out.println("値はなかったよ");
});
() -> ...
は引数を持たないラムダ式を意味するが,もちろん“値を使わないときのおまじない”だと思ってくれても構わない。
なお,Optional#ifPresentOrElse()
は Java 9 で実装されたので,Java 8 の環境では
Optional<String> someStr = Optional.empty();
someStr.ifPresent(x -> {
System.out.println(x);
}); if (!someStr.isPresent()) {
System.out.println("値はなかったよ");
}
とするしかないだろう。
なるべくOptional#ifPresent()
も使うな
できればOptional#ifPresent()
も濫用すべきではない。Optional#map()
やOptional#flatMap()
やOptional#orElse()
で十分なときが多いはずだ。Optional の世界では null チェックなんてしないのが基本なのだ。
たとえば,先に挙げたOptional#ifPresentOrElse()
の例などはこれらのメソッドを使えばより簡潔に書ける。empty
だったときの代用値を指定してアンラップするOptional#orElse()
を使用すれば,
Optional<String> someStr = Optional.empty();
System.out.println(someStr.orElse("値はなかったよ"));
これでよい。もしOptional<String>
ではなくOptional<Integer>
だったら? ならばempty
でないときだけ関数適用するOptional#map()
の出番だ。
Optional<Integer> someInt = Optional.empty();
System.out.println(someInt.map(x -> x.toString()).orElse("値はなかったよ"));
あなたがメソッド参照について勉強すればさらに簡潔に書ける。
Optional<Integer> someInt = Optional.empty();
System.out.println(someInt.map(Object::toString).orElse("値はなかったよ"));
無論,一行が長いと思ったら外に変数を定義して引き出してもいい。
より多彩な例はこちらの記事などを参照。
敗北者が多すぎて敗北者向けの記事が書かれる始末
敗北者を生む自称解説記事が多いためにOptional#get()
はそもそも使わないとか,null チェック自体しないのが理想という基本の基本部分があまり知られていないので “Optional などないほうがマシである” というようなトンチンカンな記事が書かれてしまうことになる。
NullPointerException
をNoSuchElementException
にすり替えます。プログラムがクラッシュすることには変わりありません。
もし、
myOPoint
が実際の座標を保持していない場合、myOPoint.get().x
はNoSuchElementException
を投げてプログラムをクラッシュさせます。これは元のコードから何一つ良くなってはいません。なぜなら、プログラマの目的はすべてのクラッシュを回避することであって、NullPointerException
によるクラッシュだけを回避できれば良いわけではないからです。
繰り返しになりますが、コードはどちらも似たようなもので、
Optional
が従来の参照方法よりも勝っているとは言えません。
まあ,この記事の筆者もたぶん敗北者が多すぎて呆れてこういう記事を書いたのだろう。それでもこいつ物知らねえなという記述は散見されるけど。
Optional#get()
を使わざるを得ないかもしれない例
仕様の関係で例外が発生しうる処理を外側でハンドリングすることはできない。
public static int half(int even) throws Exception {
if (even % 2 == 0); else {
throw new Exception();
}
return even / 2;
}
/* ... */
Optional<Integer> someInt = Optional.of(16);
/* できる */
try {
if (someInt.isPresent()) {
System.out.println(half(someInt.get()));
}
} catch (Exception e) {
System.out.println("偶数を渡してください");
}
/* できない */
try {
someInt.ifPresent(x -> {
System.out.println(half(x));
});
} catch (Exception e) {
System.out.println("偶数を渡してください");
}
これをなんとかするために Qiita でも様々なハックが紹介されている。Either を使うことでもなんとかできると思う。モナドばんじゃい。まあ Java 公式に Either ないんですけどね。