HashMap / Hashtable / ConcurrentHashMap: null 허용 규칙

Java에서 Map을 쓸 때 키/값에서 null을 허용하는지 여부에 대해서 알아보고자 합니다.


한눈에 보기

 

컬렉션 null  null  메모
HashMap ✅ (정확히 1개) ✅ (여러 개 가능) get(k)null이면 “키 없음” 또는 “값이 null”일 수 있음 → containsKey(k)로 구분 필요
Hashtable put/getnull 사용 시 NullPointerException
ConcurrentHashMap 동시성·원자적 연산 일관성을 위해 null 금지 (compute/mergenull 반환은 제거/미삽입 신호)

 


 

왜 규칙이 다를까?

1) HashMap — 편의성 + 현대적 API

  • containsKey가 있어 get 결과가 null이어도 존재 여부를 구분 가능
  • 그래서 키 1개 null + 여러 null 값을 허용

2) Hashtable — 역사적 설계(모호성 회피)

  • 초창기(레거시) 설계에서 getnull은 “없음” 의미로 사용
  • 값 자체에 null을 허용하면 없음 vs null 값이 구분 불가 → 아예 금지로 일관성 유지

3) ConcurrentHashMap — 동시성·원자성 보장

  • 락 없는 읽기에서 null은 “부재/제거/미초기화”를 나타내는 특수 상태값과 충돌
  • putIfAbsent, computeIfAbsent, merge 같은 원자 연산의 의미를 명확히 하기 위해 키/값 모두 null 금지

 

실무 가이드 & 모범 사례

  • HashMap
    • get(k)null일 때는 반드시 containsKey(k)로 존재 확인
    • 예: 캐시에서 “미계산”과 “계산했는데 null”을 구분할 때
  • ConcurrentHashMap
    • 계산해서 채우는 캐시라면 computeIfAbsent 사용
    • compute/merge 류에서 리매핑 함수가 null을 반환하면:
      • compute: 해당 매핑 제거
      • merge: 결합 결과가 null이면 기존 매핑 제거
    • computeIfAbsent에서 매핑 함수가 null 반환: 아무 것도 저장하지 않고 null 반환(예외 아님)
  • 스레드 안전
    • 동시 접근이 필요하면 Hashtable 대신 ConcurrentHashMap 권장(성능·API 일관성 우수)

 

Q1. 왜 HashMap은 null 키를 “1개만” 허용하나요?
A. 해시 버킷 인덱싱이 불가능한 null 키를 특수 처리(고정 슬롯) 하기 때문입니다. 자연스럽게 “하나만” 저장됩니다.

Q2. ConcurrentHashMap에서 값으로 Optional<T>를 쓰면 어때요?
A. 좋은 패턴입니다. null 금지 정책과도 맞고, “부재/값 없음”을 명확히 표현할 수 있습니다.

Q3. Hashtable을 계속 써도 되나요?
A. 새로운 코드에서는 비권장입니다. 동시성이 필요하면 ConcurrentHashMap, 불필요하면 HashMap을 사용하세요.