こんにちは、かつコーチです。
「メソッドの戻り値がnullかもしれないから、呼び出し側で毎回nullチェックを書く」というのは、地味に手間がかかる作業です。
今回はそんなnullチェックの負担を減らしてくれるOptionalの使い方を解説します。
Optionalとは?
値があるかもしれないことを表す入れ物
Optional(値が存在するかもしれないし、存在しないかもしれないことを表現する入れ物クラス)は、Optional<String>のように使い、中身が空である可能性をコード上で明示できます。
戻り値の型を見ただけで「このメソッドは値が無い場合があるので、呼び出し側で対応してね」と伝えられるのが最大のメリットです。
なぜOptionalが必要なのか
Optionalが無い場合、戻り値がnullになる可能性のあるメソッドは、シグネチャだけを見てもそれが分かりません。
// 戻り値だけ見ても、nullが返る可能性があるかどうか分からない
public static String findNameById(int id) {
if (id == 1) {
return "かつコーチ";
}
return null; // 見つからない場合はnull
}
String name = findNameById(2);
System.out.println(name.length()); // 実行時エラー(NullPointerException)
呼び出し側がnullチェックを忘れると、実行時にNullPointerExceptionが発生してしまいます。
Optionalを使うと、この「nullかもしれない」という情報を型として表現でき、呼び出し側にチェックを促せます。
基本の書き方
Optionalを返すメソッドを定義する
import java.util.Optional;
public static Optional<String> findNameById(int id) {
if (id == 1) {
return Optional.of("かつコーチ");
}
return Optional.empty(); // 値が無いことを明示する
}
Optional.of(値)で値ありのOptionalを、Optional.empty()で値なしのOptionalを作ります。
値がnullかどうか分からない場合は、Optional.ofNullable(値)を使うとnullを渡してもエラーにならずに空のOptionalへ変換されます。
isPresent・getで中身を確認する
Optional<String> result = findNameById(1);
if (result.isPresent()) {
System.out.println(result.get()); // かつコーチ
} else {
System.out.println("見つかりませんでした");
}
ただし、このisPresent() + get()の組み合わせは、実はOptionalの本来の使い方としては推奨されていません(詳しくは次のセクションで解説します)。
orElse・orElseGetでデフォルト値を用意する
値が無い場合のデフォルト値をその場で指定できるのが、Optionalらしい書き方です。
Optional<String> result = findNameById(2);
String name = result.orElse("名無しさん");
System.out.println(name); // 名無しさん
orElseGetを使うと、デフォルト値の生成処理を必要になったときだけ実行できます。
String name2 = result.orElseGet(() -> {
System.out.println("デフォルト値を生成します");
return "ゲスト";
});
System.out.println(name2); // ゲスト
固定値でよいならorElse、生成コストがかかる場合はorElseGetと使い分けます。
よくあるつまずきポイント・エラー対処
isPresent + getを多用してnullチェックと変わらないコードになった
私が実際にレビューでよく見かける一次情報として、Optionalを導入したのに結局isPresentとgetばかり使い、従来のnullチェックと変わらない書き方になっているケースがあります。
// ❌Before:isPresent + getでnullチェック時代と同じ書き方になっている
Optional<String> result = findNameById(1);
if (result.isPresent()) {
String name = result.get();
System.out.println("こんにちは、" + name + "さん");
} else {
System.out.println("ユーザーが見つかりません");
}
このコードは動きますが、Optionalが持つ「値の有無に応じた処理を関数的に書ける」という利点を活かせていません。
さらに、isPresent()を確認せずにget()を呼んでしまうと次の例外が発生します。
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
// ✅After:ifPresentOrElseで値の有無に応じた処理を関数的に書く
Optional<String> result = findNameById(1);
result.ifPresentOrElse(
name -> System.out.println("こんにちは、" + name + "さん"),
() -> System.out.println("ユーザーが見つかりません")
);
ifPresentOrElse(Java 9以降)を使うと、値がある場合・無い場合の処理をそれぞれラムダ式で渡すだけで済み、get()を直接呼び出す必要が無くなります。
「Optionalを見たら、まずisPresent + getではなくorElse系やifPresentOrElseが使えないか検討する」という習慣が身につくと、コードがぐっとシンプルになります。
応用・一歩先の使い方
map・filterで値の変換とチェックを連結する
OptionalはStream APIのようにmapやfilterで処理を連結できます。
Optional<String> result = findNameById(1);
String message = result
.filter(name -> name.length() > 3) // 4文字以上のみ通過
.map(name -> name + "さん") // 値があれば変換
.orElse("該当なし");
System.out.println(message); // かつコーチさん
途中で条件に合わなかったり値が無かったりした場合は、nullチェックを挟まなくても自動的にorElseの値に流れてくれます。
メソッドの引数やフィールドの型としては使わない
Optionalは便利ですが、Javaの設計者は「戻り値専用」として使うことを推奨しています。
// ❌避けるべき:フィールドや引数にOptionalを使う
public class User {
private Optional<String> nickname; // 非推奨
}
public void greet(Optional<String> name) { // 非推奨
}
Optional自体もオブジェクトであるため、フィールドや引数に使うと、それ自体がnullになりうる(Optional型の変数にnullが入る)という本末転倒な状況を生みかねません。
フィールドや引数では、これまで通り@Nullableなどのアノテーションやnullチェックで対応し、Optionalは戻り値の型に限定して使うのが実務での定石です。
まとめ
この記事のポイント
- Optionalは値が存在しないかもしれないことを型で表現する入れ物クラス
Optional.of・Optional.empty・Optional.ofNullableで生成するorElse・orElseGetでデフォルト値を、ifPresentOrElseで値の有無に応じた処理を書けるisPresent + getばかり使うとOptional導入のメリットが薄れるため、関数的な書き方を優先する- Optionalは戻り値専用として使い、フィールドや引数には使わないのが定石
次に読むべき記事
- ジェネリクスの境界(extends・super)
- Iteratorの仕組みと拡張for文との関係
タグ: Java, 中級者向け, コレクション