Java

[Java] Map.getOrDefault()가 예외를 던진다고?

누구세연 2025. 4. 16. 22:40

Map.getOrDefault()를 사용하다가 Exception을 만난 경험이 있다면, 이 글이 도움이 될 수 있습니다.
많은 사람들이 "getOrDefault는 키가 없을 때만 기본값을 사용한다"라고 생각하지만, 실제 동작은 다릅니다.
이 글에서는 getOrDefault()의 진짜 동작 방식을 알아보고, 안전한 대안까지 정리해 보겠습니다. 👩🏻‍💻

 

예상 못한 예외, 어디서부터 잘못된 걸까?

다음 코드에서 NumberFormatException이 발생했습니다.

Long no = noMap.getOrDefault(update.getNo(), Long.valueOf(update.getNo()));

에러 메시지는 아래와 같았습니다.

java.lang.NumberFormatException: For input string: "qwer1234"

noMap에 키가 존재하지 않으면 update.getNo()를 Long으로 바꿔서 기본값으로 사용하려던 코드였습니다. 그런데 값이 "qwer1234"처럼 UUID 형태의 문자열이라면, Long.valueOf(...)에서 바로 터지게 됩니다...😥

 

 

getOrDefault()는 "게으르지 않다"

많은 개발자들이 getOrDefault()를 다음과 같은 의미로 이해하곤 합니다.

“키가 없을 때만 기본값을 사용한다.”

하지만 실제 동작 방식은 그렇지 않습니다. 기본값으로 전달한 인자도 무조건 먼저 평가됩니다..

즉, 이 코드는

Long.valueOf(update.getParentNo()) // 먼저 실행됨!

이 실행된 결과를 getOrDefault의 두 번째 인자로 넘깁니다.
키가 존재하든 말든 Long.valueOf()는 실행됩니다!

 

 

getOrDefault() 내부 구현은??

getOrDefault 내부 코드

내부적으로도 defaultValue는 평가가 완료된 상태로 들어갑니다. 즉, 함수나 람다처럼 "필요할 때 실행"되는 구조가 아닙니다.

 

안전한 대안 ✅

조건문으로 명확하게 분기 처리하는 게 가장 안전합니다.

Long no;
if (noMap.containsKey(update.getNo())) {
    no = noMap.get(update.getNo());
} else {
    no = Long.valueOf(update.getNo());
}

또는 함수형 스타일이 익숙하다면

Long no = Optional.ofNullable(noMap.get(update.getNo()))
                        .orElseGet(() -> Long.valueOf(update.getNo()));

orElseGet()은 람다를 필요할 때만 실행하므로 Long.valueOf(...) 호출이 지연됩니다.

 

 

Java의 Map.getOrDefault()는 게으른(Lazy) 친구가 아닙니다.
기본값도 무조건 실행하므로, 부작용 가능성이 있는 코드는 절대 직접 넣지 말아야 합니다.
특히 다음과 같은 경우는 꼭 분기 처리하거나 orElseGet을 사용합시다!
- 숫자 파싱 (Long.valueOf, Integer.parseInt)
- 외부 API 호출
- I/O 또는 DB 접근
- 무거운 연산