Optional.orElse vs orElseGet 차이와 NPE 방지: 실무 규칙 7가지
두 메서드의 핵심 차이는 평가 시점입니다.
orElse는 항상 인자를 먼저 계산하고,
orElseGet은 필요할 때만 Supplier를 호출합니다.
이 차이 하나가 성능과 예외(특히 NPE)에 큰 영향을 줍니다.
orElseGet.orElseGet이 안전합니다.orElse도 좋습니다.문제
아래 코드는 config.getDefaultPath()가 시간이 오래 걸리거나 내부에서 NPE가 날 수 있습니다.
값이 이미 있을 때도 orElse는 기본값 식을 항상 먼저 실행하기 때문이죠.
// 값이 있어도 getDefaultPath()가 호출됨
String path = Optional.ofNullable(config.getPath())
.orElse(getDefaultPath()); // eager
원인
orElse(V other)의 other는 미리 평가된 값입니다(즉시 계산).orElseGet(Supplier<? extends V>)는 필요한 경우에만 실행되는 지연 평가입니다.
해결(안전한 코드)
// 값이 없을 때만 Supplier 호출
String path = Optional.ofNullable(config.getPath())
.orElseGet(() -> getDefaultPath()); // lazy
기본값 생성이 가볍고 상수라면 orElse("default")처럼 간단히 쓰면 됩니다. 무거운 계산, DB/I-O, 객체 생성, 로그 출력 등은 orElseGet으로 넘겨 두세요.
실무 규칙 7가지
- 무거운 기본값이면
orElseGet. - 상수/가벼운 값이면
orElse. orElseThrow는 값이 필수일 때 명확합니다(예외 타입 지정).- 기본값 생성 과정에서 다른 값이
null일 수 있으면orElse는 예기치 않은 NPE의 원인이 됩니다 →orElseGet. - 체이닝이 길어지면
map/flatMap으로 단계별 의도를 드러내세요. get()남용 금지. 되도록ifPresent/orElse*로 처리.- 로그/메트릭은
ifPresentOrElse(Java 9+)로 깔끔히 분기.
예제 모음
1) 캐싱된 값이 있으면 그대로, 없으면 계산
String token = cache.get(userId) // may be null
.map(Optional::of).orElseGet(() -> {
String t = issueNewToken(userId); // heavy
cache.put(userId, t);
return Optional.of(t);
}).orElse("N/A");
2) 리스트 첫 요소의 필드 꺼내기(기본값 포함)
String firstName = users.stream().findFirst()
.map(User::getName)
.orElse("anonymous");
3) 기본값이 무거운 경우
// orElse: heavyDefault()가 값이 있어도 항상 실행됨(지양)
Foo foo = Optional.ofNullable(maybe)
.orElseGet(() -> heavyDefault()); // 권장
4) orElseThrow로 필수값 보장
User user = userRepo.findById(id)
.orElseThrow(() -> new NotFoundException("user " + id));
orElse(map.get(k)) 형태에서 map이 null이면, 값이 있어도 orElse 인자를 계산하는 순간 NPE가 터질 수 있습니다. 항상 orElseGet(() -> safeGet(map,k))처럼 감싸 주세요.간단 비교 차트(예시)
아래 막대 그래프는 값이 존재(PRESENT)와 값이 없음(EMPTY) 두 상황에서의 상대 비용을 설명하기 위한 예시입니다. 존재할 땐 orElseGet이 유리하고, 없을 땐 두 방식의 비용이 비슷합니다.

관련 글
참고 문서
👉 1편: ArrayList vs LinkedList: 언제 무엇을 쓰나 (+O(1) 착각 5가지)
👉 2편: 제네릭 와일드카드 완전정복(PECS 암기팁 포함)
👉 3편: Stream API: for→stream 리팩터링 10가지(성능/가독 균형)
👉 4편: Optional.orElse vs orElseGet 차이와 NPE 방지
👉 5편: java.time 제대로 쓰기(타임존/포맷 실수 7가지)
👉 6편: Java Record vs Lombok DTO 선택 기준
👉 7편: NIO.2로 폴더 스캔/감시 구현(FileVisitor/WatchService)
👉 8편: ExecutorService 스레드풀 사이즈/큐 전략
'Java & Spring' 카테고리의 다른 글
| Java Record vs Lombok DTO 선택 기준: 언제 무엇을 쓸까 (0) | 2025.09.28 |
|---|---|
| java.time 제대로 쓰기: 타임존/포맷 실수 7가지와 안전한 사용법 (0) | 2025.09.28 |
| Stream API: for→stream 리팩터링 10가지(성능/가독 균형) (0) | 2025.09.28 |
| 제네릭 와일드카드 완벽 마스터: PECS(Producer Extends, Consumer Super) 암기팁 (0) | 2025.09.28 |
| ArrayList vs LinkedList: 언제 무엇을 써야하나 (+O(1) 착각 5가지) (0) | 2025.09.27 |