JavaのOptionalについて考える
nullチェックし忘れる
変数に対してnullチェックを行うのは常套手段ではあるものの、チェックのし忘れなど、しばしばバグを仕込んでしまいます。
// nullチェック
if (obj != null) {
// objはnullではない
}
特にソースコードが複雑になってくると、「この変数はnullになる可能性があるのだろうか」と毎回調査しなければなりません。この作業はけっこうな負担となるため、結果、とりあえず全変数に対してnullチェックしてしまいがちです。チェックしたからにはエラーハンドリングも本来は書かないと…
@Nullableアノテーション
Javaには変数がnullになる可能性があるかどうかを表現する方法として@Nullableアノテーション、@NotNullアノテーションがあります。
ただし、このアノテーションはあくまで注意書きであって強制力はありません(ライブラリによっては例外が発生するものもあるらしい)。例えば、下記例では@NotNullな引数にnullを渡してもコンパイルできてしまいます。
void func(@NotNull String str) {
// Something to do.
}
// 警告は出るがコンパイルできてしまう!
func(null);
また、アノテーションが付いていない無印の変数はどっちなの?という曖昧さも生じます。すべての変数にアノテーションを付けるのは、それはそれで面倒です。
僕が求めるものは、Nullableであることが明確で、nullチェックを強制できることです。
SwiftのOptional
一方、SwiftにはOptional型があります。Optionalとは、null(Swiftではnil)かもしれない値を表現する型です。
Optionalには以下の利点があると考えています。
Nullableな変数であることを型で表現できる
nullチェックを強制できる。Swiftはif letやguard letでUnwrapしないと値を取り出せない(Forced unwrappingもあるが…)
Nullableを型で表現できるため、かなり分かりやすいコードになります。
変数の型がOptionalだったらnullになる可能性があることが明らかであり、コンパイラも解釈してくれます。
*SwiftのOptionalについて、ここでは詳しくは書きません。
JavaのOptional
この便利なOptional、JavaでもJava 8からjava.util.Optionalクラスが追加され、使えるようになりました。
しかし、SwiftのOptionalに慣れていると、このJavaのOptionalは使い勝手が微妙だなと思うところがいくつかありました。
Optional.ofは必要?
Optional.ofにnullを渡すとNullPointerExceptionが発生します。Nullableを扱いたいのにnullを渡すと例外が発生するなんて…。このファクトリメソッドを使う機会はあるのでしょうか。
String str = null;
Optional<String> opt = Optional.of(str); // -> NullPointerException
Nullableを扱うためにはOptional.ofNullableを使います。
String str = null;
Optional<String> opt = Optional.ofNullable(str);
ムダにメソッド名が長くなっている気がします。
get()はnullチェックを強制しない
get()は、Optionalが内包する値がnullでないならば、その値を返し、nullならば、NoSuchElementExceptionが発生します。
しかし、NoSuchElementExceptionは非検査例外のため、try-catchしなくてもコンパイルエラーになりません。これではnullチェックを強制したことにはなりません。
String str = null;
Optional<String> opt = Optional.ofNullable(str);
System.out.println(opt.get()); // -> NoSuchElementException
nullチェックをするためにはisPresent()を使います。
String str = null;
Optional<String> opt = Optional.ofNullable(str);
if (opt.isPresent()) {
System.out.println(opt.get());
} else {
System.out.println("null!");
}
NullableをOptional型で表現できるようになっただけでも有り難いですが、これではisPresent()によるnullチェックし忘れ問題は残ります。
チェックするのではなく、値の有無で処理を確実に記述する
上記のコードは、orElseを使ってスッキリと書くことができます。
String str = null;
Optional<String> opt = Optional.ofNullable(str);
System.out.println(opt.orElse("null!")); // -> "null!"が出力される
また、nullではないときのみ処理したい場合はifPresent()が使えます。
String str = "Test";
Optional<String> opt = Optional.ofNullable(str);
opt.ifPresent(s1 -> {
String s2 = s1 + "er";
System.out.println(s2); // -> "Tester"が出力される
});
nullではないときのみ処理し、新しいOptional変数を作りたいときはmap()を使います。
String str = "Test";
Optional<String> opt1 = Optional.ofNullable(str);
Optional<String> opt2 = opt1.map(s -> s + "er");
System.out.println(opt2.orElse("null!")); // -> "Tester"が出力される
map()は、Optionalが内包する値がnullのときはラムダ式を処理せず、空の(内包する値がnullの)Optionalを返します。
String str = null;
Optional<String> opt1 = Optional.ofNullable(str);
Optional<String> opt2 = opt1.map(s -> s + "er");
System.out.println(opt2.orElse("null!")); // -> "null!"が出力される
これらの仕様からJavaのOptionalは、nullチェックするのではなく、値が有るとき無いときの処理を確実に記述することでバグ回避するというコンセプトなのかなと僕は考えています。
シンプルなのか
JavaのOptionalには、この他にもflatMapやfilterといったメソッドが備わっていますが、僕が求めてるものに対して少しtoo muchです。
また、Optional変数を多量に扱うコードのとき、mapやflatMapを駆使して書いたコードは煩雑になりそうな気がしています(実際に書いたことはないので、わかりませんが)。
まとめ
いくつか不満点を挙げてみましたが、NullableをOptional型で表現できるのは強力なので、導入を検討してみる価値はあるかと思います。
この記事が気に入ったらサポートをしてみませんか?