1. Optional이란?
null을 직접 다루지 않고,
"값이 있을 수도, 없을 수도 있는 객체"를 감싸는 컨테이너 클래스
- Optional<T>은 null-safe한 값을 표현하는 도구
- 리턴 타입에 사용하되, 파라미터/필드에는 되도록 쓰지 말기
- orElseThrow, map, filter, ifPresent 잘 쓰면 코드 깔끔해짐
Optional<String> name = Optional.of("Mars");
Optional<String> empty = Optional.empty();
2. 왜 쓰는가?
- NullPointerException 방지
- "null 체크"를 명시적으로 강제
- 리턴값이 "있을 수도/없을 수도" 있는 경우 문서화 효과
예전 방식
User user = repository.findByEmail(email);
if (user != null) { ... }
Optional 방식
Optional<User> userOpt = repository.findByEmail(email);
userOpt.ifPresent(user -> { ... });
3. Optional 사용 방법 정리
| of(T) | 절대 null 아닌 값을 감쌀 때 사용 (null이면 NPE 발생) |
| ofNullable(T) | null 가능성을 허용 |
| empty() | 값이 없는 Optional 생성 |
| isPresent() / isEmpty() | 값 존재 여부 확인 |
| get() | 내부 값 꺼내기 (⚠️ 값 없으면 예외 발생 → 비추) |
| orElse(default) | 값 없을 때 기본값 제공 |
| orElseGet(Supplier) | 지연 실행으로 기본값 제공 |
| orElseThrow() | 값 없을 때 예외 던짐 |
| map() / flatMap() | 값이 있을 경우 변환 |
| filter(Predicate) | 조건 만족 여부로 Optional 유지/제거 |
4. 실무 예시들
리턴 타입에 사용 (가장 많이 씀)
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
호출부:
userService.findByEmail("mars@gmail.com")
.ifPresent(user -> doSomething(user));
기본값 처리: orElse, orElseGet
String name = userOpt.map(User::getName).orElse("익명");
orElse는 항상 실행됨
orElseGet(() -> ...)은 값이 없을 때만 실행됨 → 성능상 유리
예외 처리
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("해당 유저 없음"));
매우 흔한 패턴으로 실무에서 if (user == null)을 대체
map / flatMap 활용
Optional<String> name = userOpt.map(User::getName);
Optional<String> city = userOpt
.flatMap(user -> Optional.ofNullable(user.getAddress()))
.map(Address::getCity);
| 리턴 타입이 null 가능할 때 | ✅ Optional 적극 사용 |
| 파라미터에 Optional 사용 | 🔴 비추 (코드 가독성 ↓) |
| 필드에 Optional 사용 | 🔴 비추 (JPA @Entity에서 절대 금지) |
| 외부 API 응답 파싱 | ✅ null-safe하게 사용 가능 |
| 내부에서 즉시 사용하는 임시 값 | ❌ 굳이 감싸지 말 것 |
❗ 실무에서 주의할 점
- get()은 거의 쓰지 마세요. (ifPresent, orElseThrow 사용 권장)
- Optional<T>을 파라미터로 받는 건 지양 → 그냥 @Nullable 쓰는 게 더 낫다
- JPA 엔티티 필드에 Optional 사용 ❌ → Hibernate가 못 읽음
예제 1: 기본 null 체크 → Optional
before (전통적인 null 검사)
User user = userRepository.findByEmail(email);
if (user != null) {
return user.getName();
} else {
return "알 수 없음";
}
after (Optional 리팩토링)
return userRepository.findByEmail(email)
.map(User::getName)
.orElse("알 수 없음");
null 체크도 없어지고, 가독성도 높아짐
예제 2: null이면 예외 던지기
before
User user = userRepository.findById(id);
if (user == null) {
throw new NotFoundException("유저 없음");
}
return user;
after
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("유저 없음"));
orElseThrow()는 가장 실무에서 자주 쓰이는 Optional 방식 중 하나
예제 3: 중첩된 null 검사
before
if (user != null) {
Address addr = user.getAddress();
if (addr != null) {
return addr.getCity();
}
}
return "Unknown";
after
return Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
null 안전하게 중첩 탐색 가능 → NPE 걱정 끝!
예제 4: ifPresent로 조건부 실행
before
User user = userRepository.findByEmail(email);
if (user != null) {
logger.info(user.getName());
}
after
userRepository.findByEmail(email)
.ifPresent(user -> logger.info(user.getName()));
예제 5: 기본값 orElse vs orElseGet 차이
User user1 = userOpt.orElse(createDefaultUser()); // ❌ createDefaultUser()는 항상 실행됨
User user2 = userOpt.orElseGet(() -> createDefaultUser()); // ✅ 없을 때만 실행됨
orElse는 항상 실행되고, orElseGet은 조건부 실행됨
무거운 연산이거나, 디폴트 객체 생성할 때는 orElseGet 추천!
| 기본 null 체크 | .map(...).orElse(...) |
| 예외 던지기 | .orElseThrow(...) |
| 중첩 null 접근 | .map(...).map(...).orElse(...) |
| 조건부 실행 | .ifPresent(...) |
| 디폴트 객체 지연 생성 | .orElseGet(...) |
'Java > Spring Boot' 카테고리의 다른 글
| Swagger와 API 명세 (1) | 2025.03.31 |
|---|---|
| Spring Boot Test 관련 (JUnit, Mockito, AssertJ,Spring Boot Test, Testcontainers ) (0) | 2025.03.31 |
| JPQL과 QueryDSL (0) | 2025.03.31 |
| JPA (3) | 2025.03.31 |
| OAuth 구현 (0) | 2025.03.28 |