Optionalはnullチェックのif文を減らす便利API・・・そんな風に考えていた時期が俺にもありました
nullを含むIntegerのリストと要素を取得するIntegerList
クラスを例とすると。
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を使用しない場合
/**
* 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を使用する場合
以下のように書くことができます。
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します。
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));
}
}
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の使い方としては、基本的にメソッドの戻り値としてだけ、となりフィールドには使わないほうがいいということになります。
値をこれでラップすることで直接いじることを禁止し、それによって「値が返ってこないかもしれない」ことを前提にしたプログラムの構築を構文上強制するところにメリットがあります。要するに、nullチェックを強制させるための構文(クラス)ですね。
re:僕にとってMaybe / Nullable / Optional が、どうしてもしっくりこないわけ。
JavaのOptionalは、メソッドの戻り値として使うことが想定されているらしく、引数やフィールドに使うことは想定されていないそうです。 JavaのOptionalは参照型なので、Optional自体がnullになりうるという点を考えると、引数に使うべきではないというのもある程度納得できます
Javaには、“ある値を返すが、特定の場合だけ値を返せない”というメソッドがよくある。
例えばjava.util.Map#get()は、キーがあればそれに紐付く値を返すが、キーが無い場合はnullを返す。
例えばString#indexOf()は、見つけた文字の位置を返すが、見つからなかった場合は-1を返す。
そういったメソッドの戻り値を表現するのが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に対するコーディングの考察が詰まっており、参考になりました。