숏컷 코드
Optional<User> user = findUser(id);
String name = user.map(User::getName)
.orElse("anonymous");문법
어떤 Optional 패턴을 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 값이 없을 수 있는 반환값 | Optional<T> |
| 값 있으면 변환, 없으면 기본값 | map(...).orElse(...) |
| 변환 함수가 Optional 반환 | flatMap(...) |
| 값 없으면 예외 | orElseThrow(...) |
| 값 있을 때만 수행 | ifPresent(...) |
Optional: 없을 수 있음을 타입으로 드러내기
Optional<T>은 값이 있을 수도 없을 수도 있다는 사실을 타입으로 표현하는 래퍼입니다. 반환값이 null일 때 이를 그냥 반환하면 호출자가 null 체크를 알아서 해야 하지만, Optional을 쓰면 "없을 수 있다"는 사실이 API에서부터 명시됩니다.
// null 반환 — 호출자가 null 체크를 알아야 함
User findUser(long id) { ... }
User u = findUser(id);
if (u != null) { ... } // 놓치면 NPE
// Optional 반환 — "없을 수 있음"이 타입에 표현됨
Optional<User> findUser(long id) { ... }
Optional<User> u = findUser(id);
u.ifPresent(user -> doSomething(user));map / orElse로 안전하게 값 꺼내기
Optional에서 값을 꺼낼 때는 map, flatMap, filter, orElse, orElseGet, orElseThrow, ifPresent를 조합합니다. 이 방법들은 null 체크를 명시적으로 표현하면서도 분기 로직을 줄여 줍니다.
// 값이 있으면 변환, 없으면 기본값
String name = findUser(id)
.map(User::getName)
.orElse("anonymous");
// 없을 때 예외
User user = findUser(id)
.orElseThrow(() -> new UserNotFoundException(id));
// 있을 때만 동작
findUser(id).ifPresent(u -> sendWelcomeEmail(u));flatMap — 중첩 Optional 펼치기
map은 변환 함수가 Optional을 반환할 때 Optional<Optional<T>>를 만듭니다. flatMap을 쓰면 중첩 없이 Optional<T> 하나로 펼쳐집니다.
// findAddress()가 Optional<Address>를 반환하는 상황
Optional<User> user = findUser(id);
// ❌ map 사용 — Optional<Optional<Address>> 가 됨
Optional<Optional<Address>> nested = user.map(User::findAddress);
// ✅ flatMap 사용 — Optional<Address> 로 평탄화
Optional<Address> address = user.flatMap(User::findAddress);
// 체인 예시: user → address → city
String city = findUser(id)
.flatMap(User::findAddress)
.map(Address::getCity)
.orElse("unknown");Optional 남용 주의: 잘 맞는 곳과 그렇지 않은 곳
Optional은 메서드 반환값에서 "없을 수 있음"을 표현할 때 가장 잘 맞습니다. 필드 타입, 메서드 파라미터, List 같은 컨테이너의 원소에는 어울리지 않습니다. 직렬화(Jackson 등)에서도 별도 설정이 필요합니다.
// ✅ 잘 맞는 곳 — 메서드 반환값
Optional<User> findByEmail(String email) { ... }
// ❌ 필드 타입으로 쓰면 무거움
class User {
Optional<String> nickname; // 권장하지 않음 — nullable String이 더 자연스러움
}
// ❌ 파라미터로 쓰면 호출이 어색
void update(Optional<String> name) { ... } // 권장하지 않음 — 오버로드나 @Nullable 활용체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 메서드가 값을 못 찾을 수 있을 때 | Optional<T> 반환 |
| 값이 있으면 변환, 없으면 기본값 | .map(...).orElse(...) |
| 변환 함수가 Optional을 반환할 때 | .flatMap(...) |
| 없을 때 예외 | .orElseThrow(...) |
| 있을 때만 동작 실행 | .ifPresent(...) |
| 필드 타입, 파라미터, 컬렉션 원소 | 일반 null 또는 @Nullable 권장 |
주의할 점
Optional.get()만 쓰면 null 체크를 다른 형태로 옮긴 것에 불과합니다. 항상 안전한 추출 메서드를 사용하세요.
// ❌ get()만 사용 — 값이 없으면 NoSuchElementException
Optional<User> user = findUser(id);
String name = user.get().getName(); // 없으면 예외!
// ✅ 안전한 추출 패턴 사용
String name = findUser(id)
.map(User::getName)
.orElse("anonymous");
// ✅ 없으면 예외를 던져야 한다면 명시적으로
User user = findUser(id)
.orElseThrow(() -> new UserNotFoundException(id));참고 링크
1 sources