티스토리 뷰

🍀 Spring Boot

Optional & 체이닝

James Wetzel 2025. 2. 6. 12:35
728x90
반응형

Java의 Optional을 사용하면 null 체크를 명시적으로 하지 않고도 안전하게 값을 다룰 수 있습니다. 특히 체이닝(chaining) 기법을 통해 여러 단계의 연산을 한 줄로 연결할 수 있어 가독성이 좋고 오류 가능성을 줄일 수 있습니다. 아래에서 Optional 체인을 어떻게 사용하는지에 대해 자세히 설명하겠습니다.


1. 기본 사용법

Optional은 주로 메서드의 리턴값으로 사용되어, 반환값이 null일 가능성이 있을 때 클라이언트가 안전하게 처리할 수 있도록 도와줍니다. 예를 들어:

public Optional<User> findUserById(Long id) {
    // DB나 다른 소스에서 User를 조회한다고 가정
    User user = ...; // 조회된 user 혹은 null
    return Optional.ofNullable(user);
}

이렇게 반환된 Optional을 클라이언트에서 아래와 같이 사용할 수 있습니다.

Optional<User> optionalUser = findUserById(1L);
optionalUser.ifPresent(user -> {
    // user가 존재하는 경우 실행
    System.out.println("User name: " + user.getName());
});

2. 체이닝을 이용한 연산

Optional의 여러 메서드(map, flatMap, filter 등)를 이용하여 연속적인 연산을 수행할 수 있습니다. 이를 체이닝이라고 합니다.

2.1. map 사용하기

map은 Optional 내부의 값을 다른 값으로 변환할 때 사용합니다. 예를 들어, User 객체에서 이름을 추출하는 경우:

Optional<String> userName = findUserById(1L)
    .map(User::getName);

위 코드에서 findUserById(1L)이 빈 Optional일 경우, 이후의 map 연산은 수행되지 않고 최종 결과는 빈 Optional이 됩니다.

2.2. flatMap 사용하기

flatMap은 내부에서 또 다른 Optional을 반환하는 경우에 사용합니다. 예를 들어, User 객체에서 Address 객체를 Optional로 반환하는 메서드가 있다고 가정하면:

public Optional<Address> getAddress() {
    return Optional.ofNullable(address);
}

이를 체인으로 연결하면:

Optional<String> city = findUserById(1L)
    .flatMap(User::getAddress)  // User -> Optional<Address>
    .map(Address::getCity);     // Address -> city (String)

이처럼 flatMap을 사용하면 Optional이 중첩되는 문제를 피할 수 있습니다.

2.3. filter 사용하기

filter는 Optional 내부의 값을 조건에 따라 걸러내는 역할을 합니다. 예를 들어, 특정 조건을 만족하는 User만 처리하고 싶을 때:

Optional<User> adultUser = findUserById(1L)
    .filter(user -> user.getAge() >= 18);

조건을 만족하지 않으면 최종 Optional은 빈 값이 됩니다.

2.4. 최종 결과값 처리

체이닝된 Optional의 결과값을 얻거나 기본값을 지정할 때는 아래 메서드들을 사용합니다.

  • orElse(T other): Optional이 비어있을 경우 다른 값을 반환합니다.
  • orElseGet(Supplier<? extends T> supplier): Optional이 비어있을 경우 Supplier에서 제공하는 값을 반환합니다.
  • orElseThrow(): Optional이 비어있으면 예외를 던집니다.

예시:

String name = findUserById(1L)
    .map(User::getName)
    .orElse("Default Name");

User user = findUserById(1L)
    .orElseThrow(() -> new IllegalArgumentException("User not found"));

3. 실전 예제

실제 Spring Boot 애플리케이션에서 Optional 체인을 사용한 예제입니다. 예를 들어, 게시글(Post) 객체에서 작성자(User) 정보를 가져오고, 작성자의 이메일을 추출하는 경우:

// Post 엔티티
public class Post {
    private User author;
    // getter, setter 등
}

// User 엔티티
public class User {
    private String email;
    // getter, setter 등
}

// 서비스 레이어에서 Optional 체인 사용
public String getAuthorEmail(Long postId) {
    return findPostById(postId)  // Optional<Post> 반환
        .map(Post::getAuthor)     // Optional<User>로 변환
        .map(User::getEmail)      // Optional<String>로 변환
        .orElse("no-email@example.com");
}

public Optional<Post> findPostById(Long postId) {
    // DB 조회 로직 (예: JPA Repository 사용)
    Post post = ...; // 게시글 조회
    return Optional.ofNullable(post);
}

이 코드는 다음과 같은 과정을 거칩니다.

  1. findPostById(postId)로 Optional를 받아옵니다.
  2. map(Post::getAuthor)를 통해 Optional에서 Optional로 변환합니다.
  3. 다시 map(User::getEmail)로 Optional에서 Optional로 변환합니다.
  4. orElse("no-email@example.com")으로 값이 없을 경우 기본 이메일을 반환합니다.

이처럼 Optional 체인을 활용하면 null 체크와 관련된 복잡한 코드를 간결하게 작성할 수 있습니다.


결론

  • map: Optional 내부 값을 다른 값으로 변환할 때 사용합니다.
  • flatMap: 내부 값이 Optional일 때 중첩을 피하기 위해 사용합니다.
  • filter: 조건에 따라 Optional 내부 값을 걸러냅니다.
  • orElse, orElseGet, orElseThrow: 최종 결과가 없는 경우 기본값을 제공하거나 예외를 발생시킵니다.

이러한 체이닝 방식을 통해 코드가 보다 선언적이고 명확해지며, null 체크로 인한 오류를 방지할 수 있습니다. 추가 질문이 있으시면 언제든지 문의해주세요!

728x90
반응형