Java | Java 詳細・モダン文法:Optional – Optional.map

Java Java
スポンサーリンク

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

ここでは、

  1. user が null かもしれないので if でチェックする
  2. 中身があるときだけ getName().toUpperCase() を呼ぶ
  3. ないときは 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) を考えます。

  1. Optional の中身 "hello" を取り出す
  2. 関数 String::length を適用して 5 を得る
  3. それを 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) を考えます。

  1. 中身がないので、関数は呼ばれない
  2. そのまま 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() { ... }
}
Java

null ベースだと、こうなりがちです。

User user = findUserOrNull();

String zip = null;
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        zip = address.getZipCode();
    }
}
Java

Optionalmap を使うと、こう書けます。

Optional<User> maybeUser = findUser();

Optional<String> maybeZip =
        maybeUser
                .map(User::getAddress)
                .map(Address::getZipCode);
Java

user がいなければ最初から空。
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::getNameUser -> 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>
Java

flatMap は、「関数が 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("ゲスト");
Java

isPresent()get() のコンビは、「Optional をわざわざ null 的に扱っている」状態です。
せっかく Optional を使うなら、map / flatMap / orElse / orElseThrow で完結させる方が、設計として筋が良いです。

「変換」は map、「なかったときの扱い」は orElse 系

Optional を使うときは、役割を分けて考えるとスッキリします。

変換したいとき → map / flatMap
なかったときのデフォルトやエラーを決めたいとき → orElse / orElseGet / orElseThrow

この二段構えで考えると、

  1. まず map で欲しい形に変換して
  2. 最後に orElse などで「なかったときどうするか」を決める

というパターンが自然に身につきます。


まとめ:Optional.map を自分の言葉で言うなら

あなたの言葉で Optional.map を説明するなら、こうです。

Optional.map は、“中身があるときだけ関数を適用して、結果をまた Optional に包んで返す”メソッド。
中身がなければ何もせずに空のまま流れていくので、null チェックとメソッド呼び出しのセットを、安全で読みやすいパイプラインとして書けるようにしてくれる。」

タイトルとURLをコピーしました