Java
optional
java8

【Java】Optionalの正しい使い方を学ぶ

More than 1 year has passed since last update.

Optionalはnullチェックのif文を減らす便利API・・・そんな風に考えていた時期が俺にもありました

nullを含むIntegerのリストと要素を取得するIntegerListクラスを例とすると。

IntegerList.java
import java.util.List;
import java.util.Arrays;

public class IntegerList {
    // nullが含まれるIntegerのリスト
    private final static List<Integer> INTEGER_LIST = Arrays.asList(new Integer[]{1, 2, null});

    public static Integer get(int index) {
        return INTEGER_LIST.get(index);
    }
}

上記のIntegerListから数値要素を取得し、100を足して出力するMainクラスを用意します。
IntegerList#getの返却値がnullの可能性を考慮し、nullの場合は-1を設定します。

Optionalを使用しない場合

Main.java
/**
 * IntegerListからは値を取得し、100を足して出力する
 **/
public class Main {
    public static void main(String[] args) throws Exception {
        Integer value = IntegerList.get(2);
        // nullの場合は-1を設定する
        if (value == null) {
            value = -1;
        }
        value += 100;
        System.out.println("結果:" + value);
    }
}


結果:99

Optionalを使用する場合

以下のように書くことができます。

Main.java
import java.util.Optional;

/**
 * IntegerListからは値を取得し、100を足す
 **/
public class Main {
    public static void main(String[] args) throws Exception {
        // IntegerListクラスより要素を取得する。nullの場合は-1を設定する。
        Integer value = Optional.ofNullable(IntegerList.get(2)).orElse(-1);
        value += 100;
        System.out.println("結果:" + value);
    }
}

if文がなくなり1行で書けました。

コードがすっきりした!Optional便利!!・・・という話で終わってはいけないみたいです。

何が問題か

クラス間の関係を疎結合にするならば、
呼び出し側のMainクラスがIntegerList#getの戻り値をOptional化するのは違和感がでてきます。
IntegerList#getの戻り値がnullとなる可能性があることを知っているからこそ、Optional化するコードを書くことができました。
知っているということは、関係が多少なりとも密であるということ。
そして、逆に、nullとなる可能性に気付かなければOptional化もnullチェックも書かれず、NullPointerExceptionの発生を許してしまいます。

解決案

呼び出し元が返却値をOptional化するのでなく、呼び出し先が戻り値としてOptionalをreturnします。

IntegerList.java
import java.util.List;
import java.util.Arrays;
import java.util.Optional;

public class IntegerList {
    // nullが含まれるIntegerの配列
    private final static List<Integer> INTEGER_LIST = Arrays.asList(new Integer[]{1, 2, null});

    // 戻り値にnullが含まれる可能性があるためOptionalでラップして返却する
    public static Optional<Integer> get(int index) {
        return Optional.ofNullable(INTEGER_LIST.get(index));
    }
}
Main.java
import java.util.Optional;

/**
 * IntegerListからは値を取得し、100を足す
 **/
public class Main {
    public static void main(String[] args) throws Exception {
        // IntegerListクラスより要素を取得する。nullの場合は-1を設定する。
        Optional<Integer> optionalValue = IntegerList.get(2);
        Integer value = optionalValue.orElse(-1) + 100;
        System.out.println("結果:" + value);
    }
}

Optionalを戻り値にすることで、IntegerList#getの戻り値はnullの可能性があるということを明示し、かつ、呼び出し元にnullを考慮したコーディングを強制することができます。
これで呼び出し元でnullの判定を書き忘れてNullPointerExceptionが発生することを予防できます。

参考と引用

上記でうまく伝わらなかったなら、以下を参照していただければよいと思います。

OptionalがSerializableではない話と使い方まとめ

そこで、Optionalの使い方としては、基本的にメソッドの戻り値としてだけ、となりフィールドには使わないほうがいいということになります。

Java8でnullではなくOptional型を利用する

値をこれでラップすることで直接いじることを禁止し、それによって「値が返ってこないかもしれない」ことを前提にしたプログラムの構築を構文上強制するところにメリットがあります。要するに、nullチェックを強制させるための構文(クラス)ですね。

re:僕にとってMaybe / Nullable / Optional が、どうしてもしっくりこないわけ。

JavaのOptionalは、メソッドの戻り値として使うことが想定されているらしく、引数やフィールドに使うことは想定されていないそうです。 JavaのOptionalは参照型なので、Optional自体がnullになりうるという点を考えると、引数に使うべきではないというのもある程度納得できます

Java Optional

Javaには、“ある値を返すが、特定の場合だけ値を返せない”というメソッドがよくある。
例えばjava.util.Map#get()は、キーがあればそれに紐付く値を返すが、キーが無い場合はnullを返す。
例えばString#indexOf()は、見つけた文字の位置を返すが、見つからなかった場合は-1を返す。
そういったメソッドの戻り値を表現するのがOptionalの役割。

Java 8 のOptional を使ってみる

Optionalの利用のメリットはその値はないかもしれないということをコードに明示できることです。上の例では、取得したleftやrightはOptionalなので、気をつけずともemptyの場合の処理の記述を強制させられてしまうのです。

10 Tips To Handle Null Effectively

As I already said, Optional was designed to indicate missing return values. One tempting case that it was not designed for and surely is not necessary is class’ fields.
英語ですがnullに対するコーディングの考察が詰まっており、参考になりました。