Optional.map を一言でいうと
Optional.map は
「中身が“あるときだけ”変換し、なければ何もしないで空のまま返す」
ためのメソッドです。
null チェックしてからメソッドを呼ぶ、というお決まりパターンを、if (x != null) x = x.xxx() ではなくoptional.map(xxx)
という形で書けるようにするための道具だと思ってください。
まずは「null チェック版」との対比でイメージする
null で書くとどうなるか
ユーザーがいるかもしれないし、いないかもしれない状況で、
「ユーザー名を大文字にしたい」という処理を考えます。
null ベースで書くと、こうなります。
User user = findUserOrNull(); // 見つからなければ null
String upperName = null;
if (user != null) {
upperName = user.getName().toUpperCase();
}
Javaここでは、
userが null かもしれないので if でチェックする- 中身があるときだけ
getName().toUpperCase()を呼ぶ - ないときは
upperNameは null のまま
という流れになっています。
Optional.map で書くとどうなるか
同じことを Optional で書き直します。
Optional<User> maybeUser = findUser(); // 見つからなければ Optional.empty()
Optional<String> maybeUpperName =
maybeUser
.map(User::getName)
.map(String::toUpperCase);
Javaここで起きていることは、さっきの if とまったく同じです。
中身があるときだけ User::getName が呼ばれ、
その結果がまた Optional に包まれ、
さらに中身があるときだけ String::toUpperCase が呼ばれる。
中身が最初から空なら、何も呼ばれずに最後まで Optional.empty() のまま流れていきます。
「あるときだけ変換する」「ないときは何もしない」を、
if 文ではなく map の連鎖で表現している、というのがポイントです。
Optional.map の動きを丁寧に分解する
中身がある場合
Optional.of("hello").map(String::length) を考えます。
- Optional の中身
"hello"を取り出す - 関数
String::lengthを適用して5を得る - それを
Optional.of(5)に包んで返す
結果は Optional<Integer> で、中身は 5 です。
コードで確認するとこうです。
Optional<String> maybeText = Optional.of("hello");
Optional<Integer> maybeLength =
maybeText.map(String::length);
System.out.println(maybeLength); // Optional[5]
Java中身がない場合
Optional.<String>empty().map(String::length) を考えます。
- 中身がないので、関数は呼ばれない
- そのまま
Optional.empty()を返す
結果は Optional<Integer> ですが、中身は空のままです。
Optional<String> maybeText = Optional.empty();
Optional<Integer> maybeLength =
maybeText.map(String::length);
System.out.println(maybeLength); // Optional.empty
Javaここが一番大事なポイントです。
「中身がないときに、わざわざ何かする必要がない」
「“あるときだけ”関数を適用してくれる」
この性質のおかげで、if (x != null) を書かなくてよくなります。
map をつなげると「安全なパイプライン」になる
User → Address → ZipCode みたいなチェーン
よくある「ネストしたオブジェクト」の例で見てみます。
class User {
Address getAddress() { ... }
}
class Address {
String getZipCode() { ... }
}
Javanull ベースだと、こうなりがちです。
User user = findUserOrNull();
String zip = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
zip = address.getZipCode();
}
}
JavaOptional と map を使うと、こう書けます。
Optional<User> maybeUser = findUser();
Optional<String> maybeZip =
maybeUser
.map(User::getAddress)
.map(Address::getZipCode);
Javauser がいなければ最初から空。address が null なら、その時点で空。
どこかで「ない」状態になったら、その先の map は全部スキップされます。
「途中のどこかで null だったら NPE」という不安が、
「途中のどこかで空になったら、そのまま空で流れていく」という安心に変わるイメージです。
map と flatMap の違いも軽く触れておく
map は「普通の値」を返す関数用
map に渡す関数は、基本的に「T を U に変える関数」です。
Optional<User> maybeUser = findUser();
Optional<String> maybeName =
maybeUser.map(User::getName); // User -> String
Javaここで User::getName は User -> String なので、map の結果は Optional<String> になります。
flatMap は「Optional を返す関数」用
もし User がすでに Optional<Address> を返すような API を持っていたらどうでしょう。
class User {
Optional<Address> getAddressOptional() { ... }
}
Javaこのときに map(User::getAddressOptional) をすると、型は Optional<Optional<Address>> になってしまいます。
そこで使うのが flatMap です。
Optional<Address> maybeAddress =
maybeUser.flatMap(User::getAddressOptional); // User -> Optional<Address>
JavaflatMap は、「関数が Optional を返すときに、二重にならないように平らにしてくれる」メソッドです。
map は「普通の値を返す関数」
flatMap は「Optional を返す関数」
と覚えておくと、Optional まわりの設計がかなり楽になります。
Optional.map を使うときの設計ポイント
if で unwrap しないで、そのまま map する
やりがちな悪い書き方はこれです。
Optional<User> maybeUser = findUser();
String name;
if (maybeUser.isPresent()) {
name = maybeUser.get().getName();
} else {
name = "ゲスト";
}
Javaこれを map で書き直すと、こうなります。
String name =
findUser()
.map(User::getName)
.orElse("ゲスト");
JavaisPresent() と get() のコンビは、「Optional をわざわざ null 的に扱っている」状態です。
せっかく Optional を使うなら、map / flatMap / orElse / orElseThrow で完結させる方が、設計として筋が良いです。
「変換」は map、「なかったときの扱い」は orElse 系
Optional を使うときは、役割を分けて考えるとスッキリします。
変換したいとき → map / flatMap
なかったときのデフォルトやエラーを決めたいとき → orElse / orElseGet / orElseThrow
この二段構えで考えると、
- まず
mapで欲しい形に変換して - 最後に
orElseなどで「なかったときどうするか」を決める
というパターンが自然に身につきます。
まとめ:Optional.map を自分の言葉で言うなら
あなたの言葉で Optional.map を説明するなら、こうです。
「Optional.map は、“中身があるときだけ関数を適用して、結果をまた Optional に包んで返す”メソッド。
中身がなければ何もせずに空のまま流れていくので、null チェックとメソッド呼び出しのセットを、安全で読みやすいパイプラインとして書けるようにしてくれる。」
