[Java] Optional 잘 쓰고 계신가요 ?

HenryHong·2026년 1월 13일

java

목록 보기
2/15
post-thumbnail

Java 8의 Optional은 내부 구현과 설계 철학을 이해하면 언제, 어떻게 써야 하는지가 훨씬 또렷해집니다.

Optional의 내부 구현 개요

Optional은 매우 얇은 래퍼 객체입니다.
핵심은 “값이 있거나(empty) 둘 중 하나의 상태를 표현하는 불변 객체”라는 점입니다.

  • 내부 필드는 보통 하나의 value 레퍼런스로 구성됩니다.
  • Optional.empty()는 내부적으로 싱글턴 인스턴스 하나를 재사용합니다.
  • Optional.of(T value)null이 아닌 값을 감싼 새로운 Optional을 생성합니다.

따라서 Optional 하나를 만든다고 해서 복잡한 구조가 생기는 것은 아니지만, 필드로 남발하면 래퍼 객체가 불필요하게 많이 생긴다는 문제가 있습니다.


Optional.of / ofNullable / empty

Optional의 생명 주기는 보통 다음 세 가지 팩토리 메서드에서 시작합니다.

Optional.of(T value)

Optional<String> name = Optional.of("jun");
  • valuenull이면 NullPointerException을 던집니다.
  • “여기서는 절대 null이 아니다”라는 강한 계약을 표현할 때 사용합니다.

Optional.ofNullable(T value)

Optional<String> name = Optional.ofNullable(maybeNullName);
  • valuenull이면 Optional.empty()를, 아니면 Optional.of(value)를 반환합니다.
  • DB 조회 결과, 외부 API 응답처럼 null일 수 있는 값을 감쌀 때 대표적으로 사용합니다.

Optional.empty()

Optional<String> name = Optional.empty();
  • “값이 없다”는 상태를 표현하는 싱글턴 인스턴스를 반환합니다.
  • 여러 곳에서 공유하지만, 내부 값이 없기 때문에 불변성과 스레드 안전성에 문제가 없습니다.

이 구조 덕분에 Optional은 “값의 부재”를 null 대신 타입 수준에서 표현할 수 있습니다.


Optional.map의 동작 방식

Optional.map은 내부 값이 있을 때만 매핑 함수를 적용하고, 없으면 그대로 empty를 유지합니다.

Optional<User> userOpt = findUser();
Optional<String> emailOpt = userOpt.map(User::getEmail);
  • 값이 존재하면: UserString으로 변환된 결과를 새 Optional에 감싸서 반환합니다.
  • 값이 없으면: 아무 함수도 호출하지 않고 Optional.empty()를 그대로 반환합니다.

구현 관점에서 보면 다음과 비슷한 구조입니다.

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return Optional.empty();
    }
    U result = mapper.apply(value);
    return Optional.ofNullable(result);
}

여기서 중요한 특징은 mappernull을 반환해도 NPE 대신 empty로 바꿔 준다는 점입니다.
즉, Optional 내부에는 “절대 null이 들어가지 않는다”는 불변식이 유지됩니다.


JDK 설계 철학: “일반적인 Maybe 타입이 아니다”

Optional은 하스켈의 Maybe나 스칼라의 Option과 비슷해 보이지만, 설계 철학은 훨씬 더 제한적입니다.

JSR‑335 논의와 Brian Goetz의 코멘트에서 반복적으로 나온 키워드는 다음과 같습니다.

  • 목적은 라이브러리 메서드의 반환 타입에서 “결과 없음(no result)”을 표현하는 제한적인 메커니즘이다.
  • 범용 Maybe 타입이나, 컬렉션/필드/파라미터까지 침투하는 타입으로 설계하지 않았다.
  • Optional은 “잠재적인 결과를 담고, 존재 여부를 검사하고, 없을 때 대체값을 제공하는” 아주 제한된 추상화로 유지되어야 한다.

이 철학 때문에 다음과 같은 방향성이 명확해집니다.

❌ Java 8 Optional, 실무에서 피해야 할 사용 방식

도메인 클래스의 필드로 Optional을 사용하는 것

다음과 같은 코드를 본 적이 있을지도 모릅니다.

public class User {
    private Optional<String> name;
    private Optional<Integer> age;
}

언뜻 보면 “name이나 age가 없을 수도 있다”는 의미를 분명히 해주는 것 같지만, 이는 Optional의 의도에 맞지 않은 사용입니다.
그 이유는 다음과 같습니다.

1. Optional은 직렬화를 지원하지 않는다

Optional 클래스는 Serializable을 구현하지 않습니다.
따라서 JPA 엔티티나 DTO 같은 직렬화 대상 객체에 Optional 필드를 선언하면 런타임 오류나 직렬화 이슈를 야기할 수 있습니다.
이는 스프링 기반 애플리케이션이나 REST API 응답 객체를 다루는 실무 환경에서는 치명적인 문제입니다.

2. 불필요한 메모리 오버헤드

Optional은 단일 값을 감싼 래퍼(wrapper) 객체로, 단순한 참조 필드보다 부가적인 메모리를 사용합니다.
많은 수의 엔티티 객체가 힙 메모리에 적재되는 환경에서는 이런 오버헤드가 무시하기 어렵습니다.

3. 불균일한 API의 문제

도메인 객체 내부에 Optional 필드를 사용하면 getter, setter의 형태가 일정하지 않게 됩니다.
예를 들어, getName()Optional<String>을 반환할 때도 있고, 단순히 String을 반환할 때도 있다면
호출자 입장에서 API 일관성이 깨지고, 객체 사용법에 대한 혼란이 생깁니다.


✅ Optional이 적절히 사용되는 위치

Optional“값이 없을 수 있음을 표현하기 위한 반환값의 컨테이너” 역할로 사용하는 것이 가장 이상적입니다.
즉, 메서드의 반환 타입으로만 사용하는 것이 일반적인 규칙입니다.

public Optional<User> findById(Long id) {
    return Optional.ofNullable(userRepository.find(id));
}

이렇게 하면 호출자는 Optional의 다양한 메서드(isPresent(), ifPresent(), orElse(), orElseGet(), orElseThrow())를 통해
명시적으로 값의 존재 여부를 다룰 수 있습니다.


⚡ Optional의 의도된 사용 패턴

아래는 Optional이 추구하는 코딩 스타일을 보여주는 전형적인 예시입니다.

userRepository.findById(id)
    .filter(User::isActive)
    .map(User::getEmail)
    .ifPresent(emailService::sendVerification);

이런 코드는 다음과 같은 장점을 가집니다.

  • 명시적이며, null 검사를 직접 하지 않아도 된다.
  • 함수형 메서드 체이닝을 통해 로직이 간결하고 선언적으로 표현된다.
  • 메서드의 반환 타입만으로도 값이 없을 가능성을 호출자에게 전달할 수 있다.

이는 Optional이 설계된 함수형 프로그래밍 스타일에 부합하는 대표적인 활용 방식입니다.


🚫 Anti-pattern 요약

구분설명비추천 이유
도메인 필드에서 사용Optional 타입의 변수 선언직렬화 불가, 메모리 낭비, API 불균질
컬렉션 내부에서 Optional 사용List<Optional<T>>복잡한 중첩 구조, 의미 중복
메서드 파라미터로 사용Optional<T> param호출자 입장에서 불필요한 감싸기 필요

[참고]
https://nipafx.dev/design-java-optional/
https://dzone.com/articles/optional-anti-patterns
https://mangkyu.tistory.com/203
http://verhoevenv.github.io/2016/08/30/TIL-Optional-map.html
https://homoefficio.github.io/2019/10/03/Java-Optional-%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%93%B0%EA%B8%B0/

profile
주니어 백엔드 개발자

0개의 댓글