Optional.getでNoSuchElementExceptionになる原因と安全な値の取り出し方を解説する記事のアイキャッチ。

Java

Optional.getでNoSuchElementExceptionになる原因|安全な値の取り出し方

AIによる要約

Optional.getは値が存在するときだけ使え、emptyならNoSuchElementExceptionです。直前にisPresentを確認すれば動きますが、値の取り出しと分岐が離れると読みづらくなります。必須ならorElseThrowで業務例外へ、既定値ならorElse・orElseGet、変換ならmap、存在時だけ処理するならifPresentを使います。Optionalを使っても、0件を正常・404・業務エラーのどれにするかは仕様として決める必要があります。

新人SE
新人SE
RepositoryのfindById(id).get()でNoSuchElementExceptionになりました。
ポンコツSE
ポンコツSE
検索結果がOptional.emptyだったためです。0件時の扱いをorElseThrowなどで明示してください。

Optionalは値がない可能性を型で表しますが、無条件にget()を呼ぶと、その情報を無視しているのと同じです。テストデータに対象IDがある間は動き、削除済みや不正IDで初めてNoSuchElementExceptionになります。

重要なのはgetを別メソッドへ置き換えることだけではありません。検索0件を画面の404、業務上の未登録、処理対象なし、システム異常のどれにするか決め、適切な例外や戻り値へ変換します。

この記事のポイント

  • Optional.getはemptyでNoSuchElementExceptionになる
  • 必須値はorElseThrowで例外の意味を明示する
  • 既定値はorElseとorElseGetを使い分ける
  • 値の変換はmapでつなげられる
  • 0件時の扱いはRepositoryではなく利用側の仕様で決める

getは値の存在を保証しない

Optional.getは、値がある場合にその値を返します。emptyの場合はNoSuchElementException: No value presentです。戻り値がOptionalである時点で、呼び出し側に0件対応が必要だと示されています。

Optional<User> result = repository.findById(id);
User user = result.get(); // emptyならNoSuchElementException

getを使った例外は、どのIDが見つからなかったか、業務上どう扱うべきかを表しません。低レベル例外が共通ハンドラで500になると、単なる未存在がシステム障害に見えます。

必須ならorElseThrowで業務例外へ変換する

対象が必ず必要な処理では、見つからない場合の例外を明示します。例外へIDを持たせるとログやエラーレスポンスを組み立てやすくなります。

User user = repository.findById(id)
        .orElseThrow(() -> new UserNotFoundException(id));

例外生成は値がない場合だけ実行されます。Controllerで404へ変換するのか、Serviceで業務エラーにするのか、既存の例外設計へ合わせます。

isPresentとgetを重ねすぎない

次の書き方は安全に見えますが、Optionalを通常のnullチェックと同じ形で扱い、処理が長くなります。

Optional<User> optional = repository.findById(id);
if (optional.isPresent()) {
    User user = optional.get();
    sendMail(user.getEmail());
}

存在時だけ処理するならifPresent、値を変換して返すならmap、必須ならorElseThrowの方が意図を表せることがあります。ただし複雑な業務分岐を無理にチェーンへ詰めず、読みやすさを優先します。

存在時だけ処理するifPresent

対象がなければ何もしないことが仕様なら、ifPresentで表せます。たとえば任意のキャッシュ削除や補助通知です。

repository.findById(id)
        .ifPresent(user -> notificationService.send(user));

注文取消対象がないのに何もしない、という処理は利用者へ成功と見える可能性があります。無視が正しい場面に限定します。

mapで値を変換する

Optionalの中身から別の値を取り出すならmapを使えます。途中でnullを返すとemptyになるため、nullが混ざる設計も確認します。

String email = repository.findById(id)
        .map(User::getEmail)
        .orElseThrow(() -> new EmailNotFoundException(id));

ユーザー未存在とメール未設定を同じEmailNotFoundExceptionにしてよいかは別問題です。原因を分ける必要があるなら、段階を分けて明示します。

orElseとorElseGetの違い

既定値を返す場合、値があってもorElseの引数は先に評価されます。DB検索や重い生成処理を置くならorElseGetを使います。

User user = cachedUser
        .orElseGet(() -> repository.findDefaultUser());

単純な定数ならorElseで十分です。どちらも『値なしを既定値へ置き換える』設計なので、未存在を隠してよい場面か確認します。

Optionalをフィールドや引数へ広げすぎない

Optionalは主に戻り値で値なしを表す用途に向きます。Entityフィールドやメソッド引数へ大量に使うと、シリアライズやフレームワークとの相性、呼び出しコードの複雑化が起きます。

プロジェクト規約に従い、引数の必須・任意はオーバーロードやForm、コマンドクラスで表すことも検討します。Optionalを使えばnull問題がすべて消えるわけではありません。

JUnitで存在・未存在を分ける

RepositoryをモックするServiceテストでは、Optional.ofだけでなくOptional.emptyを返すケースを必ず用意します。例外型と識別子も確認します。

@Test
void 利用者が存在しない場合は業務例外() {
    when(repository.findById("U999"))
            .thenReturn(Optional.empty());

    UserNotFoundException exception = assertThrows(
            UserNotFoundException.class,
            () -> service.getUser("U999"));

    assertEquals("U999", exception.getUserId());
}

現場レビューでよくある指摘

Optionalのレビューではgetを禁止するだけでなく、値なしが正常か異常か、どの層で何へ変換するかを確認します。

// レビューコメント例
findByIdはOptional.emptyを返すため、getではNoSuchElementExceptionになります。
未存在時の業務例外をorElseThrowで明示してください。

// レビューコメント例
isPresentの直後にgetしています。存在時だけの処理ならifPresent、
必須ならorElseThrowの方が意図を表せます。

// レビューコメント例
orElseの引数でDB検索しているため、値があっても実行されます。
遅延実行が必要ならorElseGetを使ってください。

Optionalチェーンを短くすることより、0件時の利用者体験と業務状態を説明できることを優先します。存在・未存在の両方をテストし、低レベル例外をそのまま500にしません。

提出前のセルフチェック

レビュー前に確認すること

  • Optional.getを無条件に呼んでいないか
  • 0件が正常か異常か決めたか
  • 必須ならorElseThrowで例外を明示したか
  • 存在時だけならifPresentが合うか
  • mapで異なる未存在原因を潰していないか
  • orElseで重い処理を実行していないか
  • Optionalをフィールドや引数へ広げすぎていないか
  • Optional.emptyのテストがあるか

Optionalと例外処理を実践的に学ぶ参考書

Optionalはラムダ、例外、Repositoryの戻り値とつながります。実践編で周辺APIも含めて確認すると、単なるget禁止ルールから一歩進んだ判断ができます。

書籍「スッキリわかるJava入門 実践編 第5版」の表紙
商品画像:Amazon.co.jp
PR Java基礎学習後の若手向け

スッキリわかるJava入門 実践編 第5版

中山 清喬 (著), 株式会社フレアリンク (監修)

基礎文法の次に必要な、現場寄りのJava知識を補う。

コレクション、ジェネリクス、ラムダ式、ストリームなど、業務コードで出会いやすい機能を入門編の次に学べます。

  • Java基礎の次に何を学ぶか迷っている
  • コレクションやStreamを整理したい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

Optional.getは値がなければNoSuchElementExceptionになります。必須値はorElseThrow、既定値はorElse・orElseGet、変換はmap、存在時だけの処理はifPresentを検討します。

ただしAPI選択より先に、0件を正常、404、業務エラーのどれにするか決めます。Optional.emptyのテストを追加し、低レベル例外を利用者へそのまま見せないでください。

-Java
-, ,