なんかピンと来なかった
Scalaで初めてOption型に出会い、その時に「なぜOptionを使うのか?」を調べてもなかなかピンと来なくて、いまOptionalを使う利点の一つに気がつけたので、書こうと思います。
なぜnullがダメなのか
初めて出てきた概念だったので、「Optionalの利点」ばかり調べていました。
ですが、「nullのダメな理由」分かったとき「なぜOptionalを使うのか」が分かってきました。
僕が思うnullが悪い理由は「nullがどんな型にもなれる」ことです。
String str = null;
Integer num = null;
int[] array = null;
なぜ「nullがどんな型にもなれる」ことが悪いかというと、「メソッドが呼び出せる保証が失われる」からです。
具体的に言えば、Stringなら文字の長さが取得できるlength()が使える保証があるはずです。ですが、strがnullであれば、length()は呼び出せず、エラーしてしまいます。
str.length();
このエラーの原因を「コンパイル時に型エラーとして検出することができない」ことが問題なのだと思います。
まとめると、strがString型ならString型が持っているlength(), charAt()などのメソッドが呼び出せる保証していると思います。
strがnullなら、メソッドが呼びだせずにエラーし、メソッドが呼び出せる保証を壊してしまいます。そしてこれをコンパイル時にはチェックできず、実行している最中にいきなりエラー(Javaならヌルポ)が発生します。
Optionalを使う理由
「コンパイル時に型エラーとして検出することができない」ことが問題なので、「コンパイル時に型のエラーとして検出できる」ようにすればいいと思います。
そこで、型エラーとして検出できるように新しい型を導入したくなります。
「あること」と「ないこと」を表す型の導入です。これが「Optional型」だと思います。
つまり、Optionalを使う理由はnullが抱える問題の「コンパイル時に型のエラーとして検出できない」ことを解決することだと思います。
Optionalを使ってみる
Listをインデックスで指定した要素を取得するメソッドを作ることにします(Javaで書きます)
注意点は、指定した「要素がない場合」があることです。この「要素がない場合を」nullで扱った方法が以下の方法です。
nullを使ってしまう方法
String getAt(List<String> list, int index){
// indexがlistの範囲のとき
if(0 <= index && index < list.size()){
String elem = list.get(index);
return elem;
} else {
return null; // 範囲外なのでnullを返す
}
}
使うときはこんな感じです
String elem = getAt(list, 9); // nullが返るかもしれない
nullが戻り値の問題点
要素がないときは「Stringなnull」が返ってきてしまいます。このgetAt()を使うときは**nullが返ってくることを常に意識**してプログラムを書く必要があります。
一度nullが返るかもしれないメソッドを呼べば、その戻り値を使うたびにnullであることを常に意識があります。
Optionalを使う
Optional<String> getAtOpt(List<String> list, int index){
// indexがlistの範囲のとき
if(0 <= index && index < list.size()){
String elem = list.get(index);
return Optional.ofNullable(elem); // elemをOptionalで包んで返す
} else {
return Optional.empty(); // ないので、empty()を返す
}
}
使うときはこんな感じなります
Optional<String> elemOpt = getAtOpt(list, 9);
Optionalが戻り値の利点
「nullを使ってしまう方法」とは違い、「nullが返ってくることを意識してプログラムを書く必要がない」です。
他の利点として、戻り値のOptional<String>が**「あるかも」しれない「ないかも」しれないことを表すドキュメントみたい**になっているように見えることです。他の人が作った関数でも、戻り値がOptionalなら「結果がないかもれないんだなぁ」とすぐに分かります。それに対し、nullを返す場合があることはソースコードを実際に読まないと分からないです。
おまけ - 値があるときに実行
ちなみに、Optionalの値があるときに実行させたければ、isPresentが便利です
elemOpt.ifPresent(elem -> {
System.out.println(elem); // elemOptの中身があるときに中身elemを表示
});
まとめ
nullはどんな型にでもなれる
↓
nullの時はメソッドが呼び出せる保証が失われる
(コンパイル時にチェックできない)
↓
コンパイル時にチェックできるように
「ある」、「ない」を表す新しい型を導入したくなる
↓
「ある」、「ない」を表すOptionalの導入