|

자바 Optional은 왜 자주 오해될까

자바 Optional 대표 이미지
null 대체재가 아니라 반환 의도를 드러내는 도구로 보는 편이 정확하다

자바 Optional은 종종 null을 없애는 만능 도구처럼 소개되지만, 그렇게 이해하면 오히려 더 헷갈립니다. Oracle 문서도 Optional을 결과가 없을 수 있음을 표현하는 메서드 반환 타입으로 보는 쪽에 무게를 둡니다.

이번 글에서는 Optional이 왜 유용한지보다, 왜 자주 오해되는지부터 짚겠습니다. 핵심은 필드와 파라미터에 무작정 붙이는 것이 아니라 반환 의도를 드러내는 도구로 보는 것입니다.


Optional의 원래 의도

Oracle Java SE 21의 Optional API 문서는 Optional이 메서드 반환 타입으로 쓰이도록 의도되었다고 설명합니다. 즉 조회 결과가 없을 수 있을 때, 호출자에게 그 가능성을 코드 차원에서 드러내는 역할입니다.

public Optional<User> findUser(long id) {
    return repository.findById(id);
}

이 선언을 보면 호출자는 결과가 비어 있을 수도 있다는 사실을 바로 알 수 있습니다. 이 점이 그냥 User를 반환하고 null을 돌려주는 방식보다 분명합니다.


왜 null 대체재로만 보면 틀리기 쉬울까

Optional을 null 대체재라고만 이해하면, 모든 곳에 Optional을 붙이고 싶어집니다. 하지만 Optional은 빈 값을 표현하는 방식이지, 프로그램 전체에서 null을 완전히 없애는 장치가 아닙니다.

  • 반환값에서 없음의 가능성을 분명히 드러낼 때는 좋다
  • 필드 타입으로 남발하면 객체 구조가 오히려 무거워질 수 있다
  • 파라미터에 쓰면 호출 코드가 장황해지고 의도가 흐려질 수 있다

즉 Optional은 모든 빈 값을 감싸는 상자가 아니라, 호출자에게 계약을 보여주는 타입으로 보는 편이 맞습니다.


반환 타입에서는 왜 유용할까

Optional의 가장 큰 장점은 호출자가 결과 없음 상황을 무시하기 어렵게 만든다는 점입니다.

Optional<User> user = userService.findUser(id);
user.ifPresent(value -> System.out.println(value.getName()));

물론 무조건 ifPresent()를 쓰라는 뜻은 아닙니다. 중요한 것은 Optional을 받는 순간, 호출자가 비어 있음 가능성을 생각하게 된다는 점입니다.

반대로 결과가 반드시 있어야 하는 메서드라면 Optional이 아니라 예외를 던지거나, 아예 없는 상황이 설계상 말이 안 되는지부터 다시 보는 편이 낫습니다.


필드와 파라미터에서 왜 자주 어색할까

필드에 Optional을 넣는 경우

도메인 객체나 DTO의 필드를 Optional<String>처럼 두면 읽는 사람 입장에서는 한 번 더 감싸진 의미를 계속 풀어야 합니다. 값이 없을 수 있다는 사실을 표현하려는 의도는 이해되지만, 객체 자체가 이미 상태를 표현하는 자리라면 꼭 Optional이 최선은 아닙니다.

파라미터에 Optional을 받는 경우

메서드 파라미터에 Optional을 받으면 호출자가 일부러 Optional을 만들어 넘겨야 합니다. 이 경우는 보통 오버로드, 기본값, 별도 메서드 분리 쪽이 더 읽기 쉬운 경우가 많습니다.

// 어색한 예
void updateUser(Optional<String> nickname) { ... }

// 더 자연스러운 대안 예
void updateNickname(String nickname) { ... }
void clearNickname() { ... }

orElse와 orElseGet 차이

Optional 설명에서 자주 빠지는 부분이 orElse()orElseGet() 차이입니다. 둘 다 비어 있을 때 대체값을 준다는 점은 같지만, 평가 시점이 다릅니다.

String name = optionalName.orElse(createDefaultName());
String name2 = optionalName.orElseGet(() -> createDefaultName());

orElse()는 Optional에 값이 있어도 전달된 표현식이 먼저 계산될 수 있습니다. 반면 orElseGet()은 정말 비어 있을 때만 supplier를 실행합니다. 그래서 대체값 생성 비용이 크거나 부수 효과가 있으면 orElseGet이 더 안전한 경우가 많습니다.


실전에서 기억할 규칙

  1. 결과가 없을 수 있는 메서드 반환 타입에서는 Optional을 우선 검토한다
  2. 필드 타입으로는 신중하게 쓴다
  3. 파라미터 타입으로는 보통 더 단순한 대안을 먼저 본다
  4. Optional 자체를 null로 두지 않는다
  5. 대체값 계산이 무거우면 orElseGet을 먼저 생각한다

핵심은 Optional을 많이 쓰는 것이 아니라, 없음이라는 계약을 어디에서 분명히 보여줘야 하는지 판단하는 것입니다.



실무 코드에서 더 자주 부딪히는 장면

Optional을 처음 배울 때는 예제 코드가 아주 깔끔해 보입니다. 하지만 실무에서는 repository, service, controller, DTO 경계가 다르고 프레임워크 제약도 있습니다. 그래서 Optional을 잘 쓰는 기준은 문법을 많이 아는 것이 아니라, 어디서 계약을 드러내고 어디서 단순함을 유지할지를 나누는 데 있습니다.

  • 조회 결과가 없을 수 있는 서비스 반환값에는 잘 맞는다
  • JSON 바인딩이나 엔티티 필드처럼 프레임워크가 기대하는 형태에서는 신중해야 한다
  • 메서드 호출자가 Optional을 직접 만들어 넘겨야 하는 구조는 대개 다시 설계해볼 여지가 있다

결국 중요한 것은 null을 숨기는 일이 아니다

Optional의 핵심은 null을 보기 싫게 가리는 일이 아닙니다. 오히려 없음의 가능성을 더 드러내는 일에 가깝습니다. 이 차이를 이해하면 Optional을 남발하는 코드와, 정말 필요한 자리에서 쓰는 코드를 구분하기 쉬워집니다.

마무리

자바에서 값과 계약을 어떻게 드러낼지 고민된다면 자바 String, StringBuilder, StringBuffer 차이 글처럼 타입 선택 기준을 함께 보는 것도 도움이 됩니다.

자바 Optional은 null을 무조건 없애는 도구가 아니라, 결과가 없을 수 있음을 명시하는 반환 타입으로 이해할 때 가장 자연스럽습니다. 이 관점을 잡으면 왜 Optional이 어떤 곳에서는 좋고, 어떤 곳에서는 오히려 어색한지도 같이 보입니다.

Optional을 처음부터 만능 규칙으로 삼기보다, 메서드 계약을 더 분명하게 만드는지 먼저 따져보세요. 공식 기준은 Oracle Optional API 문서에서 직접 확인할 수 있습니다.

함께보면 좋은 글