0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Java】OptionalとorElseThrowを基礎から解説!nullとの戦いに終止符を

0
Last updated at Posted at 2026-03-03

はじめに

Javaで最も有名なエラーといえばNullPointerExceptionです。

String name = null;
System.out.println(name.length()); // NullPointerException!

Java 8で導入された**Optional**は、「値があるかもしれないし、ないかもしれない」ことを型で明示し、nullを安全に扱うための仕組みです。

この記事ではOptionalの基本から、Spring Bootでよく見るorElseThrowまで段階的に解説します。

Optionalとは

Optional<T>は、「値が入っているかもしれない箱」です。

Optional<String>
├── 値あり → "太郎" が入っている
└── 値なし → 空(nullではない!)

nullとの違い

// nullの場合:使う側が常にnullチェックしないと危険
String name = findName();  // nullかもしれない
name.length();             // NullPointerExceptionの可能性!

// Optionalの場合:型が「ないかもしれない」ことを教えてくれる
Optional<String> name = findName();  // 「ないかもしれない」と型で明示
// name.length();  // コンパイルエラー!直接使えない → 安全

Optionalの生成方法

import java.util.Optional;

public class OptionalCreate {
    public static void main(String[] args) {
        // 1. 値がある場合
        Optional<String> opt1 = Optional.of("Hello");
        System.out.println(opt1);           // Optional[Hello]

        // 2. 空のOptional
        Optional<String> opt2 = Optional.empty();
        System.out.println(opt2);           // Optional.empty

        // 3. nullかもしれない値を包む(最もよく使う)
        String value = null;
        Optional<String> opt3 = Optional.ofNullable(value);
        System.out.println(opt3);           // Optional.empty

        String value2 = "World";
        Optional<String> opt4 = Optional.ofNullable(value2);
        System.out.println(opt4);           // Optional[World]
    }
}
メソッド 用途 nullを渡すと
Optional.of(値) 値が絶対にある場合 NullPointerException
Optional.empty() 空のOptionalを作る
Optional.ofNullable(値) nullかもしれない場合 Optional.empty()になる

Optionalから値を取り出す

isPresent + get(非推奨パターン)

import java.util.Optional;

public class IsPresentDemo {
    public static void main(String[] args) {
        Optional<String> opt = Optional.of("Hello");

        // 動くが、nullチェックと変わらない → 非推奨
        if (opt.isPresent()) {
            System.out.println(opt.get()); // Hello
        }
    }
}

これではOptionalを使う意味がありません。以下のメソッドを使いましょう。

orElse:値がなければデフォルト値

import java.util.Optional;

public class OrElseDemo {
    public static void main(String[] args) {
        Optional<String> opt1 = Optional.of("太郎");
        Optional<String> opt2 = Optional.empty();

        System.out.println(opt1.orElse("名無し")); // 太郎
        System.out.println(opt2.orElse("名無し")); // 名無し
    }
}

orElseGet:値がなければ処理を実行してデフォルト値を生成

import java.util.Optional;

public class OrElseGetDemo {
    public static void main(String[] args) {
        Optional<String> opt = Optional.empty();

        // Supplierで遅延評価(必要な時だけ実行される)
        String result = opt.orElseGet(() -> "デフォルト値を生成");

        System.out.println(result); // デフォルト値を生成
    }
}

orElseとorElseGetの違い

import java.util.Optional;

public class OrElseVsOrElseGet {
    public static String createDefault() {
        System.out.println("createDefault() が呼ばれた!");
        return "デフォルト";
    }

    public static void main(String[] args) {
        Optional<String> opt = Optional.of("太郎"); // 値あり

        // orElse: 値があっても createDefault() が実行される
        String r1 = opt.orElse(createDefault());
        // 出力: createDefault() が呼ばれた!

        // orElseGet: 値があれば createDefault() は実行されない
        String r2 = opt.orElseGet(() -> createDefault());
        // 出力: (なし)

        System.out.println(r1); // 太郎
        System.out.println(r2); // 太郎
    }
}

DB問い合わせなど重い処理をデフォルト値にする場合は、orElseGetを使うべきです。

orElseThrow:値がなければ例外をスロー

ここが本題です。Spring Bootで最も頻出するOptionalメソッドです。

基本

import java.util.Optional;

public class OrElseThrowDemo {
    public static void main(String[] args) {
        Optional<String> opt1 = Optional.of("太郎");
        Optional<String> opt2 = Optional.empty();

        // 値あり → そのまま返す
        String name1 = opt1.orElseThrow(() -> new RuntimeException("見つかりません"));
        System.out.println(name1); // 太郎

        // 値なし → 例外をスロー
        String name2 = opt2.orElseThrow(() -> new RuntimeException("見つかりません"));
        // RuntimeException: 見つかりません
    }
}

orElseThrowの仕組み

orElseThrowの中身は、概念的にはこうなっています。

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (値がある) {
        return ;
    } else {
        throw exceptionSupplier.get(); // Supplierから例外を取得してスロー
    }
}

引数の() -> new RuntimeException(...)は、例外を生成するSupplier(ラムダ式)です。

// この部分を分解すると
.orElseThrow(() -> new RuntimeException("not found"))

// こういうこと
Supplier<RuntimeException> supplier = () -> new RuntimeException("not found");
.orElseThrow(supplier)

値がある場合、Supplierは実行されません(例外オブジェクトは作られない)。これが効率的なポイントです。

引数なしのorElseThrow(Java 10以降)

// 引数なしだと NoSuchElementException がスローされる
String name = opt.orElseThrow();
// NoSuchElementException: No value present

Spring BootでのorElseThrow実践パターン

基本パターン

import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class TodoService {
    private final TodoRepository todoRepository;

    public Todo findById(Long id) {
        return todoRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Todo not found: id " + id));
    }
}

findByIdの戻り値はOptional<Todo>なので、orElseThrowで安全に取り出します。

カスタム例外を使うパターン(実務向け)

// カスタム例外
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String resource, Long id) {
        super(resource + " not found: id " + id);
    }
}
// Service
public Todo findById(Long id) {
    return todoRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Todo", id));
}

カスタム例外にしておくと、例外ハンドラーで統一的にエラーレスポンスを返せます。

Optionalのその他の便利メソッド

map:値があれば変換する

import java.util.Optional;

public class OptionalMapDemo {
    public static void main(String[] args) {
        Optional<String> opt = Optional.of("Hello");

        // 値があれば大文字に変換、なければ空のまま
        Optional<String> upper = opt.map(s -> s.toUpperCase());

        System.out.println(upper); // Optional[HELLO]
    }
}

ifPresent:値があれば処理を実行

import java.util.Optional;

public class IfPresentDemo {
    public static void main(String[] args) {
        Optional<String> opt = Optional.of("太郎");

        // 値がある場合のみ実行(なければ何もしない)
        opt.ifPresent(name -> System.out.println("名前: " + name));
        // 名前: 太郎
    }
}

filter:条件に合えば値を保持、合わなければ空に

import java.util.Optional;

public class OptionalFilterDemo {
    public static void main(String[] args) {
        Optional<Integer> opt = Optional.of(20);

        Optional<Integer> adult = opt.filter(age -> age >= 18);
        Optional<Integer> child = opt.filter(age -> age < 18);

        System.out.println(adult); // Optional[20]
        System.out.println(child); // Optional.empty
    }
}

メソッドの使い分け早見表

やりたいこと メソッド
値がなければデフォルト値を返す orElse("default")
値がなければ処理を実行してデフォルト値を返す orElseGet(() -> ...)
値がなければ例外をスロー orElseThrow(() -> ...)
値があれば変換する map(v -> ...)
値があれば処理を実行 ifPresent(v -> ...)
値を条件でフィルタ filter(v -> ...)

まとめ

概念 意味
Optional<T> 「値があるかもしれないし、ないかもしれない」を表す型
Optional.of(値) 値が確実にある場合に使う
Optional.ofNullable(値) nullかもしれない値を包む
Optional.empty() 空のOptionalを生成
orElse(デフォルト値) 値がなければデフォルト値
orElseThrow(Supplier) 値がなければ例外スロー
Spring Bootでの頻出 findById(id).orElseThrow(() -> new 例外(...))

Optional + orElseThrowは、Spring Bootでのデータ取得の定番パターンです。「IDで検索して、なければ例外」を安全かつ簡潔に書けます。

演習問題

問題1 ⭐(基本)

以下のnullチェックをOptionalを使って書き換えてください。

public class Exercise1 {
    public static String greet(String name) {
        if (name != null) {
            return "Hello, " + name + "!";
        } else {
            return "Hello, Guest!";
        }
    }

    public static void main(String[] args) {
        System.out.println(greet("太郎")); // Hello, 太郎!
        System.out.println(greet(null));    // Hello, Guest!
    }
}
模範解答
import java.util.Optional;

public class Exercise1 {
    public static String greet(String name) {
        return Optional.ofNullable(name)
                .map(n -> "Hello, " + n + "!")
                .orElse("Hello, Guest!");
    }

    public static void main(String[] args) {
        System.out.println(greet("太郎")); // Hello, 太郎!
        System.out.println(greet(null));    // Hello, Guest!
    }
}
  • Optional.ofNullable(name):nullかもしれない値をOptionalで包む
  • .map(n -> ...):値があればメッセージに変換
  • .orElse(...):値がなければデフォルトのメッセージ

問題2 ⭐⭐(応用)

以下のコードでfindUserOptional<String>を返すように変更し、mainメソッドではorElseThrowを使って値を取り出してください。

import java.util.HashMap;
import java.util.Map;

public class Exercise2 {
    private static Map<Integer, String> users = new HashMap<>(Map.of(
        1, "太郎",
        2, "花子",
        3, "次郎"
    ));

    public static String findUser(int id) {
        return users.get(id);  // nullが返る可能性あり
    }

    public static void main(String[] args) {
        System.out.println(findUser(1)); // 太郎
        System.out.println(findUser(99)); // null(危険!)
    }
}
模範解答
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class Exercise2 {
    private static Map<Integer, String> users = new HashMap<>(Map.of(
        1, "太郎",
        2, "花子",
        3, "次郎"
    ));

    public static Optional<String> findUser(int id) {
        return Optional.ofNullable(users.get(id));
    }

    public static void main(String[] args) {
        // 存在するID → 値が返る
        String user1 = findUser(1)
                .orElseThrow(() -> new RuntimeException("User not found: id 1"));
        System.out.println(user1); // 太郎

        // 存在しないID → 例外がスローされる
        String user99 = findUser(99)
                .orElseThrow(() -> new RuntimeException("User not found: id 99"));
        // RuntimeException: User not found: id 99
    }
}

findUserの戻り値をOptional<String>にすることで、呼び出し側にnullの可能性を明示できます。

問題3 ⭐⭐⭐(チャレンジ)

以下の要件を満たすProductServiceを作成してください。

  • findById(Long id): IDで商品を検索。なければProductNotFoundExceptionをスロー
  • findByName(String name): 名前で商品を検索。なければ"不明な商品"を返す
  • findExpensiveProduct(Long id, int threshold): IDで検索し、価格がthreshold以上ならその商品名を返す。商品がないか条件に合わなければ"該当なし"を返す
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

class Product {
    String name;
    int price;
    Product(String name, int price) { this.name = name; this.price = price; }
}

class ProductNotFoundException extends RuntimeException {
    ProductNotFoundException(Long id) { super("Product not found: id " + id); }
}

public class ProductService {
    private Map<Long, Product> products = new HashMap<>(Map.of(
        1L, new Product("ノートPC", 150000),
        2L, new Product("マウス", 3000),
        3L, new Product("キーボード", 12000)
    ));

    // ここに3つのメソッドを実装

    public static void main(String[] args) {
        ProductService service = new ProductService();

        // findById
        System.out.println(service.findById(1L).name);      // ノートPC
        // service.findById(99L);                            // ProductNotFoundException

        // findByName
        System.out.println(service.findByName("マウス"));     // マウス
        System.out.println(service.findByName("タブレット")); // 不明な商品

        // findExpensiveProduct
        System.out.println(service.findExpensiveProduct(1L, 100000)); // ノートPC
        System.out.println(service.findExpensiveProduct(2L, 100000)); // 該当なし
        System.out.println(service.findExpensiveProduct(99L, 100000)); // 該当なし
    }
}
模範解答
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

class Product {
    String name;
    int price;
    Product(String name, int price) { this.name = name; this.price = price; }
}

class ProductNotFoundException extends RuntimeException {
    ProductNotFoundException(Long id) { super("Product not found: id " + id); }
}

public class ProductService {
    private Map<Long, Product> products = new HashMap<>(Map.of(
        1L, new Product("ノートPC", 150000),
        2L, new Product("マウス", 3000),
        3L, new Product("キーボード", 12000)
    ));

    // 値がなければ例外
    public Product findById(Long id) {
        return Optional.ofNullable(products.get(id))
                .orElseThrow(() -> new ProductNotFoundException(id));
    }

    // 値がなければデフォルト値
    public String findByName(String name) {
        return products.values().stream()
                .filter(p -> p.name.equals(name))
                .map(p -> p.name)
                .findFirst()
                .orElse("不明な商品");
    }

    // filter + map + orElseの組み合わせ
    public String findExpensiveProduct(Long id, int threshold) {
        return Optional.ofNullable(products.get(id))
                .filter(p -> p.price >= threshold)
                .map(p -> p.name)
                .orElse("該当なし");
    }

    public static void main(String[] args) {
        ProductService service = new ProductService();

        System.out.println(service.findById(1L).name);             // ノートPC
        System.out.println(service.findByName("マウス"));           // マウス
        System.out.println(service.findByName("タブレット"));       // 不明な商品
        System.out.println(service.findExpensiveProduct(1L, 100000));  // ノートPC
        System.out.println(service.findExpensiveProduct(2L, 100000));  // 該当なし
        System.out.println(service.findExpensiveProduct(99L, 100000)); // 該当なし
    }
}
  • findByIdorElseThrowでカスタム例外をスロー
  • findByName:Stream APIで名前検索し、orElseでデフォルト値
  • findExpensiveProductOptionalfiltermaporElseを連鎖させて、条件に合う商品名を取得

参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?