Optionalを使ったnull/空文字判定2.0


結局if文使うなら、いっそOptionalは一切使わないほうがいいよなって思ったりした。null/空文字判定をOptionalで進化させたい。

なお、以下断りがなければJava1.8以上で動作する想定です。

きっかけ

最近、文字列の「存在チェック」を行うコードでこんなのを発見して、かなり違和感があった

String uri = SystemPropertiesUtil.getPropertyOptional(Constant.URI).orElse("");

if (StringUtils.isEmpty(dkInfoGetApiUrl)) {
// nullならなんかやる
}

DBからAPIのURIを取得してきて、APIコールに使う場面です。 getPropertyOptional はOptionalを返すメソッド。
また、if文に登場する StringUtils#isEmpty はSpring Frameworkが提供しているユーティリティメソッド。

これなら普通にnull/空文字判定をすればよくて、Optionalを使わないほうがむしろシンプルでいい。

この例は少し極端だが、一般的によくある「nullではない∧空文字ではない」をOptionalを利用して使い勝手や安全性を向上させられないか、検討する。

Optionalの性質

Optionalは、Javadocによると

null以外の値が含まれている場合も含まれていない場合もあるコンテナ・オブジェクトです。

とある (Optional (Java Platform SE 8 ))。

Optional#ofNullabele でラップした時点でnull出ない場合はそのまま値を取得できるし、そうでない場合は別途処理をつけたせばよい。つまり、あえて空文字を返す理由もあまりない。

ただし、Stringのインスタンスがnullでないことと空文字でないことの双方を確認する場合、 Optional#ofNullable で初期化すると空文字でないことはチェックから抜けてしまことに留意する。

解決案

一般的に、空文字判定するときは

  • nullチェック
  • 空文字チェック

をまとめてbooleanを返すstaticメソッドで判定するケースが多いと思う。たとえば

StringUtil.java
public static boolean isNullOrEmpty(String string) {
    if (string == null || string.equals("")) return true;

    return false;
}

それを、以下のように代替できないか。
Optionalで期待した文字列が入っていることを確認する場合、よくある StringUtils に以下のようなメソッドを追加してみる。

StringUtil.java
public static Optional<String> ofPresentable(String string) {
    if (string == null || string.equals("")) return Optional.empty();

    return Optional.of(string);
}

2020-11-17:追記
コメントで別案をいただき、よかったので補足。

StringUtil.java
public static Optional<String> ofPresentable(String string) {
    return Optional.ofNullable(string)
            .map(String::trim)
            .filter(s -> !s.isEmpty());
    // Java 11以上ならisBlankが使える
    // return Optional.ofNullable(string).filter(Predicate.not(String::isBlank));
}

戻り値をOptionalでラップすることで値が存在しない可能性を明示できる。そのため、今までなら

SomeObject object = new SomeObject();
if(id == null || id.equals("")) {
    object.setId("default");
} else {
    object.setId(id);
}

としていたところを

final String idPresentable = StringUtil.ofPresentable(id).orElse("default");
SomeObject object = new SomeObject(idPresentable);

とできる。
正直、やっていることに大きな差はないのだが制御構文がなくなることで状態の変更に気を配る負荷を小さくでき、追いかけやすく変更しやすくなる。
また、nullになる可能性を明示できるため、ロジック上nullになることはありえない箇所は任意の実行時例外を投げることで予期しないエラーを防ぐこともできる。

つまり、

final String idPresentable = StringUtil.ofPresentable(id).orElseThrow(new RuntimeException("予期しない入力です"));
SomeObject object = new SomeObject(idPresentable);

100%代替できるわけではないし、向いてないユースケースもあるだろう。しかし、いくつかのケースにおいてはむしろこちらのパターンのほうが有効な気がしており、今後是非試してみたいと思っている。

おまけ

今回、強烈な課題を感じたのが文字列の処理だったが、ListやMapについても同じようなメソッドを用意することで要素存在判定、ある場合の処理を簡潔かつ安全にできそうです。

たとえば

ListUtil.java
public static <T> Optional<List<T>> ofPresentable(List<T> list) {
    if (list == null || list.size() == 0) return Optional.empty();

    return Optional.of(list);
}

リッチにするならリストの要素にnullが含まれているかも判定してもよいかもしれません。