こんにちは、さるまりんです 🐒🔧
Javaでプログラムを書いていると、Optional という型を見かけることがあります。
たとえば、データベースから1件だけデータを取得するような処理で、次のようなコードを見たことはありませんか?
Optional<User> user = userRepository.findById(id);
僕自身も、最初はデータベース処理の戻り値として見ることが多かったです。
でも Optional は、データベース専用のものではありません。
Optional は、ひとことで言うと、
「値があるかもしれないし、ないかもしれない」
という状態を表すためのものです。
今回は、Javaの Optional について、
「どう書くか」だけでなく、どう考えるものなのかを整理してみます。
Optionalは「値がないかもしれない」を表すもの
Javaでは、値がないことを null で表すことがあります。
String name = null;
ただ、null は便利な一方で、扱いを間違えると NullPointerException の原因になります。
String name = null;
System.out.println(name.length());
このコードは、name が null なのでエラーになります。
null は、String の値そのものではありません。
そのため、length() のような String のメソッドを呼び出すことができないのです。
そこで登場するのが Optional です。
Optional<String> name = Optional.empty();
Optional.empty() は、
値が入っていないOptional
を表します。
一方で、値がある場合はこう書けます。
Optional<String> name = Optional.of("さるまりん");
つまり Optional は、
String name = "さるまりん";
や、
String name = null;
のように直接値を持つのではなく、
値があるかもしれない箱
として扱うイメージです。
もともと「変数は値を持つ箱」と説明されることもあるので、少しこんがらがりそうですが、Optional はさらにその外側で「値があるかどうか」を表してくれるもの、と考えるとわかりやすいかもしれません。
JavaScriptやPHPの ?? と少し似ている?
ここで少し、他の言語と比べてみます。
JavaScriptやPHPには、?? という演算子があります。
たとえばJavaScriptなら、こんな感じです。
const displayName = userName ?? "名無し";
これは、
userName が null や undefined なら "名無し" を使う
という意味です。
PHPでも似たように書けます。
$displayName = $userName ?? '名無し';
考えている問題は、Javaの Optional と少し近いです。
どちらも、
値がないかもしれない
という状況を扱っています。
ただし、役割は少し違います。
?? は、
値がなかったときに、その場で代わりの値を決める書き方
です。
一方で、Javaの Optional は、
値があるかもしれないし、ないかもしれない状態を型として表すもの
です。
Javaで近い書き方をすると、こうなります。
Optional<String> userName = Optional.ofNullable(null);
String displayName = userName.orElse("名無し");
System.out.println(displayName);
実行すると、次のように表示されます。
名無し
orElse() は、Optional の中に値があればその値を使い、値がなければ指定した値を使います。
上のコードでは、
nullかもしれない値をOptionalにする- 取り出すときに、空だったら
"名無し"を使う
という流れになっています。
なので感覚としては、
const displayName = userName ?? "名無し";
に少し近いです。
ただ、Optional は単なる代わりの値の指定だけではありません。
メソッドの戻り値として使うことで、
この処理は値が返らない可能性がある
ということを、コードを読む人に伝える役割もあります。
Optionalを作ってみる
まずは、Optional の基本的な作り方を見てみます。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.of("さるまりん");
System.out.println(name);
}
}
実行すると、次のように表示されます。
Optional[さるまりん]
String そのものではなく、Optional に包まれていることがわかります。
値がない場合は、こうです。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.empty();
System.out.println(name);
}
}
実行結果です。
Optional.empty
このように、Optional は値がある状態と、ない状態を表せます。
nullの可能性がある値には ofNullable を使う
Optional を作るときによく使うのが、ofNullable() です。
Optional<String> name = Optional.ofNullable(getName());
ofNullable() は、渡した値が null でなければ、値ありの Optional を作ります。
渡した値が null なら、空の Optional になります。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
String rawName = null;
Optional<String> name = Optional.ofNullable(rawName);
System.out.println(name);
}
}
実行結果です。
Optional.empty
一方で、Optional.of() に null を渡すとエラーになります。
Optional<String> name = Optional.of(null);
これは NullPointerException になります。
そのため、基本的には次のように使い分けます。
- 絶対に
nullではない値 →Optional.of() nullの可能性がある値 →Optional.ofNullable()- 値がないことを明示したい →
Optional.empty()
実際のコードでは、null の可能性がある値を扱うことも多いので、まずは ofNullable() を覚えておくと使いやすいと思います。
String? のような書き方とは近い?
他の言語を見ていると、String? address のように、型の後ろに ? をつけて「この値は null になるかもしれない」と表すものがあります。
ただし、現在の一般的なJavaでは、String? address のような書き方はそのまま使えません。
String? address; // 通常のJavaではこのようには書けない
Javaにも ? を使う書き方はありますが、それは主にGenericsのワイルドカードです。
List<?> items;
この ? は「具体的な型は決まっていない」という意味で使われます。
つまり、String? address のように「この変数は null かもしれない」と表すための ? とは別物です。
Javaで近いことをしたい場合は、@Nullable のようなアノテーションを使って、IDEや静的解析ツールに「この値は null になるかもしれない」と伝える方法があります。
@Nullable
String address;
また、Java自体にも String? や String! のような形で、null の扱いを型として表そうとする動きはあります。
ただ、現時点では通常のJavaコードとして広く使えるものではありません。
そのため、この記事ではまず、今すぐ使える標準機能として Optional を見ていきます。
Optional<String> は、@Nullable String とまったく同じものではありません。
ただ、
値がない可能性を、コード上で見えるようにする
という意味では、近い考え方を持っています。
値があるときだけ処理する ifPresent
Optional の中に値があるときだけ何かしたい場合は、ifPresent() が使えます。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.of("さるまりん");
name.ifPresent(value -> {
System.out.println("こんにちは、" + value + "さん");
});
}
}
実行結果です。
こんにちは、さるまりんさん
値がない場合は、処理されません。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.empty();
name.ifPresent(value -> {
System.out.println("こんにちは、" + value + "さん");
});
}
}
この場合、何も表示されません。
ifPresent() を使うと、
if (name != null) {
System.out.println(name);
}
のような null チェックとは少し違って、
Optionalの中に値がある場合だけ処理する
と、やりたいことが見えやすくなります。
値がなければ代わりを使う orElse
値がない場合に、代わりの値を使いたいときは orElse() を使います。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.empty();
String displayName = name.orElse("名無し");
System.out.println(displayName);
}
}
実行結果です。
名無し
値がある場合は、その値が使われます。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.of("さるまりん");
String displayName = name.orElse("名無し");
System.out.println(displayName);
}
}
実行結果です。
さるまりん
orElse() は、JavaScriptやPHPの ?? と感覚的に近い部分があります。
const displayName = userName ?? "名無し";
Javaで Optional を使うなら、次のような感じです。
String displayName = optionalName.orElse("名無し");
重い処理なら orElseGet
orElse() と似たものに、orElseGet() があります。
String displayName = name.orElseGet(() -> createDefaultName());
orElseGet() は、値がないときだけ、指定した処理を実行して代わりの値を作ります。
たとえば、こういうメソッドがあるとします。
private static String createDefaultName() {
System.out.println("デフォルト名を作成しました");
return "名無し";
}
次のコードを見てみます。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.of("さるまりん");
String displayName = name.orElseGet(() -> createDefaultName());
System.out.println(displayName);
}
private static String createDefaultName() {
System.out.println("デフォルト名を作成しました");
return "名無し";
}
}
実行結果です。
さるまりん
値があるので、createDefaultName() は呼ばれません。
つまり、代わりの値を作る処理が少し重い場合や、ログ出力・外部アクセスなどを含む場合は、orElseGet() の方が向いています。
一方で、ただの固定文字列なら orElse() で十分です。
String displayName = name.orElse("名無し");
このくらいなら、orElse() の方が読みやすいですね。
値がなければ例外にする orElseThrow
値がない場合に、処理を続けられないこともあります。
たとえば、ユーザーIDからユーザーを探して、見つからなければエラーにしたい場合です。
User user = findUserById(id)
.orElseThrow(() -> new IllegalArgumentException("ユーザーが見つかりません"));
このように orElseThrow() を使うと、
値があれば取り出す。なければ例外にする。
という流れを表せます。
簡単な例で見てみます。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.empty();
String displayName = name.orElseThrow(() ->
new IllegalArgumentException("名前がありません")
);
System.out.println(displayName);
}
}
このコードは、Optional が空なので例外になります。
orElseThrow() は、実際のプログラムを書くときにかなり使いやすいです。
特に、
- 見つからなければエラーにしたい
- その先の処理では必ず値が必要
- エラー理由を明確にしたい
という場面で役立ちます。
get() をすぐ使わない
Optional には get() というメソッドもあります。
String value = name.get();
値が入っていれば取り出せます。
ただし、値がない状態で get() を呼ぶとエラーになります。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> name = Optional.empty();
String value = name.get();
System.out.println(value);
}
}
これは NoSuchElementException になります。
もちろん、絶対に値があるとわかっている場面で使うこともできるかもしれません。
ただ、Optional を使っているのに、
if (name.isPresent()) {
String value = name.get();
}
のような書き方ばかりになると、少しもったいないです。
Optional を使うなら、
name.ifPresent(value -> {
System.out.println(value);
});
や、
String displayName = name.orElse("名無し");
または、
String displayName = name.orElseThrow(() ->
new IllegalArgumentException("名前がありません")
);
のように、
値がある場合、ない場合をどう扱うか
をメソッドで表した方が読みやすくなります。
DB検索以外でも使える
Optional は、データベース検索の戻り値以外でも使えます。
たとえば、環境変数を取得する場合です。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<String> env = Optional.ofNullable(System.getenv("APP_MODE"));
String mode = env.orElse("local");
System.out.println("mode = " + mode);
}
}
環境変数 APP_MODE が設定されていなければ、local を使います。
また、文字列を数値に変換するような処理でも使えます。
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
Optional<Integer> number = parseInt("123");
number.ifPresent(value -> {
System.out.println(value * 2);
});
}
private static Optional<Integer> parseInt(String text) {
try {
return Optional.of(Integer.parseInt(text));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
}
実行結果です。
246
もし "abc" のように数値に変換できない文字列を渡した場合は、空の Optional を返せます。
Optional<Integer> number = parseInt("abc");
このようにすると、
変換できるかもしれないし、できないかもしれない
という状態を Optional で表せます。
StreamのfindFirstでもOptionalが出てくる
Optional は、Stream APIでもよく出てきます。
import java.util.List;
import java.util.Optional;
public class OptionalSample {
public static void main(String[] args) {
List<String> names = List.of("salu", "marine", "okapi");
Optional<String> result = names.stream()
.filter(name -> name.startsWith("m"))
.findFirst();
result.ifPresent(name -> {
System.out.println(name);
});
}
}
実行結果です。
marine
findFirst() は、条件に合う最初の要素を返します。
でも、条件に合う要素がない可能性もあります。
そのため、戻り値は String ではなく Optional<String> になります。
Optional<String> result = names.stream()
.filter(name -> name.startsWith("z"))
.findFirst();
この場合、z から始まる名前はないので、Optional.empty() になります。
ここでもやはり、
見つかるかもしれないし、見つからないかもしれない
という状態を Optional で表しています。
Optionalを使うと読み手に伝わる
Optional の良いところは、単にエラーを避けるだけではありません。
たとえば、次のようなメソッドがあるとします。
public User findUser(String id) {
// ユーザーを探す
}
この戻り値だけを見ると、ユーザーが必ず返ってくるのか、見つからない場合に null が返るのかが少しわかりにくいです。
一方で、戻り値が Optional<User> ならどうでしょうか。
public Optional<User> findUser(String id) {
// ユーザーを探す
}
この場合、メソッドを使う側は、
ユーザーが見つからないこともあるんだな
とわかります。
これは、コードの読みやすさにかなり影響します。
Optional は、値がない可能性を隠すのではなく、見える形にするためのものとも言えそうです。
どこでもOptionalにすればいいわけではない
便利な Optional ですが、何でも Optional にすればよいわけではありません。
たとえば、クラスのフィールドに Optional を持たせるのは、基本的には慎重に考えた方がよいかもしれません。
public class User {
private Optional<String> nickname;
}
このようにするよりも、フィールドは通常の型で持ち、必要に応じてメソッドの戻り値で Optional を使う方が自然なことが多いです。
public class User {
private String nickname;
public Optional<String> getNickname() {
return Optional.ofNullable(nickname);
}
}
また、メソッドの引数に Optional を使うのも、少し読みづらくなることがあります。
public void updateName(Optional<String> name) {
// ...
}
この場合は、呼び出し側に Optional を作らせることになります。
引数として受け取るなら、通常の型で受け取り、メソッドの中で必要に応じて扱う方がわかりやすい場合もあります。
public void updateName(String name) {
// ...
}
Optional は便利ですが、
値がない可能性を戻り値で伝える
という使い方が、まずは一番わかりやすいと思います。
AIが出したコードにOptionalが出てきたら
最近は、ChatGPTなどにJavaのコードを作ってもらうことも増えてきました。
その中で、Optional が出てくることもあります。
そのときは、まず次のことを見るとよさそうです。
- その値は本当に「ない可能性」があるのか
get()をそのまま使っていないか- 値がない場合にどうするかが書かれているか
orElse()/orElseGet()/orElseThrow()の選び方が自然か
AIが出してくれたコードは便利ですが、Optional が出てきたから安全、というわけではありません。
大切なのは、
値がなかったときにどうするのか
がコード上でちゃんと表現されているかだと思います。
まとめ
今回は、Javaの Optional について整理してみました。
Optional は、単なる null 回避の道具というより、
値があるかもしれないし、ないかもしれない状態を表すための型
です。
データベース検索の戻り値でよく見かけますが、環境変数の取得、文字列の変換、Streamの検索など、いろいろな場面で使えます。
ポイントをまとめると、こんな感じです。
Optionalは「値がないかもしれない」を表すofNullable()はnullの可能性がある値をOptionalにできるifPresent()は値があるときだけ処理できるorElse()は値がないときの代わりを指定できるorElseGet()は必要なときだけ代わりの値を作れるorElseThrow()は値がない場合に例外にできるget()の安易な使用は避けたいOptionalは戻り値で使うと意図が伝わりやすい
JavaScriptやPHPの ?? と少し似た感覚もありますが、Optional はそれよりも少し広く、値がない可能性を型として表すものです。
また、String? のような「null かもしれない型」を持つ言語と考え方が近い部分もあります。
ただし、現在の通常のJavaでは、String? address のようには書けません。
JavaのGenericsで使う ? とも別物です。
Javaでは、必要に応じて @Nullable のようなアノテーションを使うこともありますが、標準機能として値がない可能性を表したいときには、Optional<String> がひとつの選択肢になります。
「値がないかもしれない」ということを、コードの中でどう見える形にするか。
Optional を知ると、Javaのコードの読み方にちょっと幅が出るかもしれないですね。
読んでくださってありがとうございます。
それではまた!