Java Optionalを理解するための簡単なお題をやってみる

Java

Javaのコーディングをしていると、null との比較は頻繁に登場する。
あなたも、

if (userName == null) {

}

のようなコードを書いたことがあるのではないだろうか。

Java 8 からは、この null を扱うための仕組みとして Optional が導入されている。
今回は、簡単なお題を通して、この Optional を使ってみることにする。

お題: 会員情報から「表示名」を決める

仕様

ユーザーには以下の情報がある。

  • ニックネーム
  • 本名
  • メールアドレス

表示名は次の優先順で決める。

  1. ニックネームがあればそれを使う
  2. なければ本名を使う
  3. それもなければメールアドレスを使う
  4. どれもなければ "ゲスト" を返す

実行例

1

ニックネームを入力しますか? (y / n) >> y
ニックネームを入力してください >> ポンタ
本名を入力しますか? (y / n) >> y
本名を入力してください >> 山田太郎
メールアドレスを入力しますか? (y / n) >> y
メールアドレスを入力してください >> mj@example.com

ようこそ、ポンタさん!

2

ニックネームを入力しますか? (y / n) >> n
本名を入力しますか? (y / n) >> y
本名を入力してください >> 山田太郎
メールアドレスを入力しますか? (y / n) >> y
メールアドレスを入力してください >> mj@example.com

ようこそ、山田太郎さん!

3

ニックネームを入力しますか? (y / n) >> n
本名を入力しますか? (y / n) >> n
メールアドレスを入力しますか? (y / n) >> n

ようこそ、ゲストさん!

作成

一般クラス User

public class User {
    private String nickname;
    private String realName;
    private String email;

    public User(String nickname, String realName, String email) {
        this.nickname = nickname;
        this.realName = realName;
        this.email = email;
    }

    public String getNickname() {
        return nickname;
    }

    public String getRealName() {
        return realName;
    }

    public String getEmail() {
        return email;
    }
}

アプリケーションクラス:Main

次に、アプリケーションクラスとして Main クラスを作成する。
今回は Main クラス内に main メソッドと、表示名を決定する getDisplayName メソッドを用意して進める。

public class Main {
    public static void main(String[] args) {

    }

    static String getDisplayName(User user) {
        return "";
    }
}
  • 次に入力パートを作成する。Mainに以下を追記
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String nickname = null;
        String realName = null;
        String email = null;

        System.out.print("ニックネームを入力しますか? (y / n) >> ");
        String answer = sc.nextLine();
        if (answer.equals("y")) {
            System.out.print("ニックネームを入力してください >> ");
            nickname = sc.nextLine();
        }

        System.out.print("本名を入力しますか? (y / n) >> ");
        answer = sc.nextLine();
        if (answer.equals("y")) {
            System.out.print("本名を入力してください >> ");
            realName = sc.nextLine();
        }

        System.out.print("メールアドレスを入力しますか? (y / n) >> ");
        answer = sc.nextLine();
        if (answer.equals("y")) {
            System.out.print("メールアドレスを入力してください >> ");
            email = sc.nextLine();
        }

        User user = new User(nickname, realName, email);
    }

    static String getDisplayName(User user) {
        return "";
    }
}

3つの情報を受け取ってUserインスタンスを作成している。とくに問題となる点はないだろう。

次に、今回のメインとなる getDisplayName メソッドを作成する。
このメソッドでは、User オブジェクトに入っている値をもとに、画面に表示する名前を決定する。

表示名の優先順位は次の通りである。

  1. ニックネーム
  2. 本名
  3. メールアドレス
  4. どれもなければ「ゲスト」

まずは Optional を使わず、if 文で素直に実装してみる。

static String getDisplayName(User user) {
    if (user.getNickname() != null) {
        return user.getNickname();
    }

    if (user.getRealName() != null) {
        return user.getRealName();
    }

    if (user.getEmail() != null) {
        return user.getEmail();
    }
    return "ゲスト";
}

このように書けば、表示名の優先順位を順番に判定できる。
ただし、null チェックが続くため、処理の意図は単純でも少し冗長に見える。
次に、この処理を Optional を使って書き換えてみる。

import java.util.Optional;
static String getDisplayName(User user) {
    return Optional.ofNullable(user.getNickname())
            .or(() -> Optional.ofNullable(user.getRealName()))
            .or(() -> Optional.ofNullable(user.getEmail()))
            .orElse("ゲスト");
}

このコードでは、まず nicknameOptional として扱い、値がなければ realName、それもなければ email を見るようにしている。
そして、最後まで値が見つからなかった場合は orElse("ゲスト") によって "ゲスト" を返している。

if 文で書いた場合と比べると、
「値があれば使う。なければ次へ進む」
という流れが、よりメソッドチェーンとして表現されている点が特徴である。

いつStringになった??

Javaの理解が深いあなたはこう思っているかもしれない。このgetDisplayName()メソッドはStringを返さないとエラーになるはずだ。しかしreturnされている処理を見ると明示的にStringに変換している部分は見当たらない。。。これはどういうことであろうか?
これを理解するためにまずメソッドチェーンをやめて記述してみる


Optional<String> opt = Optional.ofNullable(user.getNickname());
opt = opt.or(() -> Optional.ofNullable(user.getRealName()));
opt = opt.or(() -> Optional.ofNullable(user.getEmail()));

return opt.orElse("ゲスト");

1行目

Optional<String> opt = Optional.ofNullable(user.getNickname());
  • Optional クラスの static メソッド ofNullable() に、null の可能性がある値を渡す
  • user.getNickname() に値がある場合
    opt は値あり(present)の Optional となり、その値を保持する
  • user.getNickname()null の場合
    opt は値なし(empty)の Optional となる

2行目,3行目

opt = opt.or(() -> Optional.ofNullable(user.getRealName()));
  • optがpresent -> ラムダ式は実行されず、そのままの opt が返る
  • optがempty -> realName を使って新しい Optional を作る処理が実行される
    • realNameがある -> opt は値あり(present)の Optional となり、その値を保持する
    • realNameがない ->opt は値なし(empty)の Optional となる

4行目

return opt.orElse("ゲスト");
  • opt が present の場合
    → 保持している値を返す(今回は String
  • opt が empty の場合
    → デフォルト値 "ゲスト" を返す

Mainの完成

ではアプリを仕上げよう。以下のようにMainに追記する

import java.util.Optional;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String nickname = null;
        String realName = null;
        String email = null;

        System.out.print("ニックネームを入力しますか? (y / n) >> ");
        String answer = sc.nextLine();
        if (answer.equals("y")) {
            System.out.print("ニックネームを入力してください >> ");
            nickname = sc.nextLine();
        }

        System.out.print("本名を入力しますか? (y / n) >> ");
        answer = sc.nextLine();
        if (answer.equals("y")) {
            System.out.print("本名を入力してください >> ");
            realName = sc.nextLine();
        }

        System.out.print("メールアドレスを入力しますか? (y / n) >> ");
        answer = sc.nextLine();
        if (answer.equals("y")) {
            System.out.print("メールアドレスを入力してください >> ");
            email = sc.nextLine();
        }

        User user = new User(nickname, realName, email);

        String displayName = getDisplayName(user);
        System.out.println();
        System.out.println("ようこそ、" + displayName + "さん!");

        sc.close();
    }

    static String getDisplayName(User user) {
        Optional<String> opt = Optional.ofNullable(user.getNickname());
        opt = opt.or(() -> Optional.ofNullable(user.getRealName()));
        opt = opt.or(() -> Optional.ofNullable(user.getEmail()));

        return opt.orElse("ゲスト");
    }
}

Optionalクラス

最後にOptional頻出メソッドをまとめておく

Optionalを作るメソッド

Optional.ofNullable(value)

null の可能性がある値を Optional に包む。
値があれば presentnull なら empty になる。

Optional<String> opt = Optional.ofNullable(user.getNickname());
Optional.of(value)

null ではないことが確実な値を Optional に包む。
null を渡すと例外になる。

Optional<String> opt = Optional.of("山田太郎");
Optional.empty()

値なしの Optional を明示的に作る。

Optional<String> opt = Optional.empty();

Optionalの中身を加工するメソッド

map()

値があるときだけ、中身を変換する。

Optional<String> opt = Optional.ofNullable(user.getNickname())

        .map(String::trim);
filter()

値があるときだけ、条件に合うものだけ残す。
条件に合わなければ empty になる。

Optional<String> opt = Optional.ofNullable(user.getNickname())

        .filter(s -> !s.isEmpty());

値がなければ別の候補を見るメソッド

or()

値がない場合に、別の Optional を返す処理をつなげる。
今回のお題では最も重要なメソッドである。

opt = opt.or(() -> Optional.ofNullable(user.getRealName()));

or() には値そのものではなく、Optional を返すラムダ式を渡す。
これは、必要なときだけ実行するためである。

最後に値を取り出すメソッド

orElse(value)

値があればその値を返し、なければ指定したデフォルト値を返す。

return opt.orElse("ゲスト");
orElseGet(() -> value)

値がないときだけ、ラムダ式を実行してデフォルト値を作る。

return opt.orElseGet(() -> createDefaultName());
orElseThrow()

値がなければ例外を投げる。

return opt.orElseThrow();
get()

値をそのまま取り出す。
ただし、empty の場合は例外になるため、使いどころには注意が必要である。

return opt.get();

最後に

Optional は一見すると少し書き方が独特である。しかし、null を直接追いかけるのではなく、「値がある」「値がない」という状態で考えられるようになると、コードの見え方も少し変わってくる。まずは ofNullable()or()orElse() あたりから慣れていくとよいだろう。

Java
スポンサーリンク
シェアする
mjpurinをフォローする

コメント

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