Javaのコーディングをしていると、null との比較は頻繁に登場する。
あなたも、
if (userName == null) {
}のようなコードを書いたことがあるのではないだろうか。
Java 8 からは、この null を扱うための仕組みとして Optional が導入されている。
今回は、簡単なお題を通して、この Optional を使ってみることにする。
お題: 会員情報から「表示名」を決める
仕様
ユーザーには以下の情報がある。
- ニックネーム
- 本名
- メールアドレス
表示名は次の優先順で決める。
- ニックネームがあればそれを使う
- なければ本名を使う
- それもなければメールアドレスを使う
- どれもなければ
"ゲスト"を返す
実行例
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 オブジェクトに入っている値をもとに、画面に表示する名前を決定する。
表示名の優先順位は次の通りである。
- ニックネーム
- 本名
- メールアドレス
- どれもなければ「ゲスト」
まずは 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("ゲスト");
}このコードでは、まず nickname を Optional として扱い、値がなければ 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となる
- realNameがある ->
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 に包む。
値があれば present、null なら 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() あたりから慣れていくとよいだろう。

コメント