Optional이 해결하는 문제
답을 갖지 못할 수도 있는 메서드는 Java에서 늘 어색한 선택에 직면해 왔습니다. null을 반환하고 호출하는 쪽이 검사하기를 잊지 않기를 바라는 것이죠. 보통은 잊어버리고, 그 결과 null을 반환한 메서드에서 멀리 떨어진 곳에서 NullPointerException이 나타납니다.
Optional<T>은 값을 담고 있거나 명시적으로 비어 있는 작은 상자입니다. User 대신 Optional<User>를 반환함으로써, 메서드는 그 시그니처에서 호출하는 쪽에 "이것은 아무것도 찾지 못할 수도 있다"고 말하며, 컴파일러는 그 경우를 처리하도록 이끕니다.
Optional 만들기
팩토리 메서드는 세 가지가 있으며, 알맞은 것을 고르는 것이 중요합니다.
Optional.of(value)- 값은 null이 아니어야 하며, 그렇지 않으면 즉시NullPointerException을 던집니다.Optional.ofNullable(value)- 값이 null이면 비어 있고, 그렇지 않으면 값이 있습니다. null일 수 있는 것을 감쌀 때 사용하세요.Optional.empty()- 빈 optional입니다.
흔한 실수는 null일 수 있는 값에 Optional.of(x)를 쓰는 것입니다. 그러면 목적이 무너지고, 피하려던 바로 그 예외를 던집니다. 확실하지 않다면 ofNullable을 사용하세요.
값을 안전하게 읽기
optional을 손에 넣었다면, 목표는 값이 거기 있다고 가정하지 않고 값을 꺼내는 것입니다. 무딘 도구가 get()이며, 거의 절대 사용해서는 안 됩니다.
Optional<String> name = Optional.empty();
String value = name.get(); // NoSuchElementException을 던진다 - 변장한 NPE
대신 대체 값을 제공하거나 존재 여부에 반응하세요. orElse는 비어 있을 때 기본값을 반환하고, ifPresent는 값이 존재할 때만 코드를 실행합니다.
기본값을 만드는 비용이 클 때는 orElse 대신 orElseGet(supplier)을 사용하세요. supplier는 optional이 실제로 비어 있을 때만 실행됩니다. orElse의 인수는 필요 없을 때조차 항상 평가됩니다.
"이것은 존재해야 한다"를 위한 orElseThrow
때때로 비어 있음은 정말로 오류입니다. 필수 설정 값, 데이터베이스에 있어야 하는 사용자 같은 경우죠. orElseThrow는 빈 optional을 당신이 선택한 명확한 예외로 바꿉니다.
이것은 if (opt.isPresent()) 블록보다 훨씬 읽기 좋으며, 실패가 일어나는 바로 그 지점에서 실패를 명시합니다.
map과 filter로 변환하기
진정한 이점은 체이닝에 있습니다. map은 값이 존재할 때만 함수를 값에 적용하고 빈 optional은 비어 있는 채로 둡니다. 따라서 null을 한 번도 건드리지 않고 변환합니다. filter는 값이 테스트를 통과하지 못하면 그 값을 버립니다.
매핑 함수 자체가 Optional을 반환한다면, 어색한 Optional<Optional<T>>을 피하기 위해 map 대신 flatMap을 사용하세요. 이는 스트림에서 본 map/flatMap의 구분을 그대로 반영합니다. Optional은 원소가 0개 또는 1개인 스트림과 매우 비슷하게 동작합니다.
Optional이 어울리는 곳 (그리고 어울리지 않는 곳)
Optional은 정당하게 결과를 만들어내지 못할 수 있는 메서드를 위한 반환 타입으로 설계되었습니다. 대표적인 예는 Stream.findFirst(), Map 스타일의 조회, 그리고 파싱입니다. 거기에 사용하면 API가 자신의 빈틈을 스스로 문서화합니다.
다음 용도로는 적합하지 않습니다.
- 필드. 직렬화할 수 없고 필드마다 객체를 하나씩 더합니다. 평범한 참조를 사용하고 생성자에서 검증하세요.
- 메서드 매개변수. 그러면 호출하는 쪽이 인수를
Optional.of(...)로 감싸야 하는데, 이는 오버로드나 null 허용 매개변수보다 번잡합니다. - 컬렉션.
Optional<List>이 아니라 빈List를 반환하세요. 빈 컬렉션은 이미 "아무것도 없음"을 의미합니다.
반환 타입으로 다루면, Optional은 조용한 null 버그를 빠진 경우를 처리하라는 컴파일 시점의 안내로 바꿉니다.
다음: 예외
Optional은 일상적인 "값이 없을 수도 있다"는 경우를 깔끔하게 처리하지만, 일부 실패는 정말로 예외적입니다. 열리지 않는 파일, 끊기는 네트워크, 파싱할 수 없는 입력 등이죠. Java는 그러한 것들을 예외로 모델링하며, 그것이 다음 페이지입니다.
자주 묻는 질문
Java에서 Optional이란 무엇이며 왜 사용하나요?
Optional<T>은 값을 담고 있거나 비어 있는 컨테이너입니다. 메서드의 반환 타입에서 "값이 없을 수도 있다"는 점을 명시적으로 드러내기 위해 존재하며, 그 덕분에 호출하는 쪽은 null 검사를 깜빡하고 런타임에 NullPointerException을 만나는 대신 빈 경우를 처리하도록 유도됩니다. 주로 아무것도 찾지 못하는 조회처럼 결과를 만들어내지 못할 수 있는 메서드의 반환 타입으로 사용하세요.
Optional.of와 Optional.ofNullable의 차이는 무엇인가요?
Optional.of(x)는 x가 null이면 즉시 NullPointerException을 던집니다. 값이 null이 아님을 알 때 사용하세요. Optional.ofNullable(x)는 x가 null일 때 빈 Optional을, 그렇지 않으면 값이 있는 것을 반환합니다. 값이 null일 수 있을 때 사용하세요. Optional.empty()는 빈 optional을 곧바로 줍니다.
값을 읽으려고 Optional.get()을 호출해야 하나요?
맨 get()은 피하세요. optional이 비어 있으면 NoSuchElementException을 던지는데, 이는 이름만 다른 NullPointerException일 뿐입니다. 빈 경우가 항상 처리되도록 orElse, orElseGet, orElseThrow, ifPresent, map을 선호하세요. 꼭 먼저 확인해야 한다면 isPresent()로 방어하되, 함수형 메서드가 더 깔끔합니다.