본문 바로가기
Java & Spring

Optional.orElse vs orElseGet 차이와 NPE 방지: 실무 규칙 7가지

by yamoojin83 2025. 9. 28.

 

 

Optional.orElse vs orElseGet 차이와 NPE 방지: 실무 규칙 7가지

두 메서드의 핵심 차이는 평가 시점입니다.

orElse항상 인자를 먼저 계산하고,

orElseGet필요할 때만 Supplier를 호출합니다.

이 차이 하나가 성능과 예외(특히 NPE)에 큰 영향을 줍니다.

 

한 줄 요약: 값이 있을 때 불필요한 계산을 피하려면 orElseGet.
값이 없을 때만 무거운 기본값을 만들거나 I/O를 부를 때 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가지

  1. 무거운 기본값이면 orElseGet.
  2. 상수/가벼운 값이면 orElse.
  3. orElseThrow는 값이 필수일 때 명확합니다(예외 타입 지정).
  4. 기본값 생성 과정에서 다른 값이 null일 수 있으면 orElse는 예기치 않은 NPE의 원인이 됩니다 → orElseGet.
  5. 체이닝이 길어지면 map/flatMap으로 단계별 의도를 드러내세요.
  6. get() 남용 금지. 되도록 ifPresent/orElse*로 처리.
  7. 로그/메트릭은 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)) 형태에서 mapnull이면, 값이 있어도 orElse 인자를 계산하는 순간 NPE가 터질 수 있습니다. 항상 orElseGet(() -> safeGet(map,k))처럼 감싸 주세요.
 

간단 비교 차트(예시)

아래 막대 그래프는 값이 존재(PRESENT)값이 없음(EMPTY) 두 상황에서의 상대 비용을 설명하기 위한 예시입니다. 존재할 땐 orElseGet이 유리하고, 없을 땐 두 방식의 비용이 비슷합니다.

orElse vs orElseGet 비용 비교(예시)
Illustrative: 존재 시 orElse는 인자를 미리 계산하므로 불리, orElseGet은 필요할 때만 Supplier 실행.

 

 

관련 글

참고 문서

Oracle JDK Optional API

 

 

 

👉 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 스레드풀 사이즈/큐 전략

👉 9편: CompletableFuture allOf/anyOf로 외부 API 병렬화

👉 10편: 예외 처리 베스트 프랙티스(체크/언체크, 경계 설계)