Optional로 값 없음 표현하기
메서드가 정상적으로 결과를 찾지 못할 수 있을 때 Optional은 값 있음과 없음의 두 상태를 반환 타입에 드러냅니다.
필드와 매개변수, 모든 반환값에 무조건 사용하는 범용 null 대체품은 아닙니다.
orElse는 대체값을 미리 계산하고 orElseGet은 없을 때만 Supplier를 호출합니다.
get을 바로 호출하면 없음 상태를 다시 예외로 바꿉니다.
이 절에서는 호출자가 없음에 대한 행동을 선택하도록 API를 설계합니다.
Optional은 null 값을 담는 상자가 아니라 “조회는 성공했지만 결과가 없을 수 있음”이라는 반환 계약입니다. 데이터베이스 장애처럼 조회 자체가 실패한 경우까지 empty로 바꾸면 호출자는 부재와 장애를 구분하지 못합니다. 없음과 실패를 서로 다른 경로로 유지해야 합니다.
조회 결과의 없음에 사용한다
단일 값을 찾는 메서드가 null 대신 Optional<T>를 반환하면 호출자가 map, orElse, orElseThrow로 정책을 고릅니다.
of와 ofNullable의 입력 계약이 다르다
Optional.of(null)은 즉시 실패하고 ofNullable(null)은 empty를 만듭니다.
null이 버그인지 정상 입력인지 구분합니다.
OptionalResults.java는 찾은 사용자와 찾지 못한 사용자를 같은 조회 API로 처리하되, 후속 동작은 상태에 따라 갈라지게 합니다.
import java.util.List;
import java.util.Optional;
public class OptionalResults {
record User(String id, String email) { }
static Optional<User> find(List<User> users, String id) {
return users.stream().filter(user -> user.id().equals(id)).findFirst();
}
public static void main(String[] args) {
List<User> users = List.of(new User("1", "ada@example.com"));
String email = find(users, "1").map(User::email).orElse("missing");
String absent = find(users, "2").map(User::email).orElseGet(() -> "created lazily");
System.out.println(email);
System.out.println(absent);
// find(users, "2").get(); // 실행 시 NoSuchElementException
}
}id 1은 User가 있으므로 map(User::email)이 실행되고 실제 이메일을 돌려줍니다. id 2는 empty라 map을 건너뛰고 orElseGet의 Supplier를 그때 한 번 호출합니다.
ada@example.com
created lazily존재하는 조회에는 map이 실행되고 없는 조회에서만 orElseGet의 Supplier가 호출됩니다.
map과 flatMap으로 값이 있을 때만 변환한다
Optional을 연쇄 조건문처럼 중첩하지 않고 값 변환 흐름을 연결합니다.
변환 함수가 Optional을 반환하면 flatMap을 사용합니다.
orElse와 orElseGet은 반환값이 같아도 대체값 계산 시점이 다릅니다. 파일 읽기나 객체 생성처럼 비용·부작용이 있는 대체 동작은 값이 없을 때만 실행되도록 Supplier를 사용합니다.
Optional<String> strict = Optional.of("value");
Optional<String> nullable = Optional.ofNullable(null);String eager = strict.orElse(expensiveFallback()); // fallback을 미리 호출
String lazy = strict.orElseGet(() -> expensiveFallback()); // 없을 때만 호출// 여러 결과: Optional<List<T>>보다 빈 List<T>
// 필수 필드: Optional 필드보다 생성자 검증
// static String expensiveFallback() { return "fallback"; }컬렉션과 필드에는 다른 모델을 우선한다
여러 결과는 빈 컬렉션, 필수 필드는 생성자 검증이 더 단순합니다.
Optional 필드는 직렬화와 사용 코드를 복잡하게 할 수 있습니다.
expensiveFallback 안에 출력문을 넣고 값이 있는 strict에 orElse와 orElseGet을 각각 적용해 보세요. 최종 문자열은 같아도 전자는 대체 계산을 수행하고 후자는 수행하지 않습니다. 이 차이는 대체 동작이 느리거나 외부 효과를 가질 때 중요합니다.
Optional을 받은 즉시 isPresent와 get으로 풀면 null 검사 문법만 바뀐 셈입니다. 값 변환은 map/flatMap, 기본값은 orElseGet, 반드시 있어야 하는 경계는 orElseThrow로 의도를 드러내세요. 여러 원소 결과는 빈 컬렉션이 자연스럽고, DTO 필드는 해당 도메인의 선택성 모델을 직접 표현하는 편이 낫습니다.
Optional은 없음 상태가 정상인 단일 조회 결과에서 호출자의 선택을 강제할 때 가장 유용합니다.