Java에서 Map을 쓸 때 키/값에서 null을 허용하는지 여부에 대해서 알아보고자 합니다.
한눈에 보기
| 컬렉션 | null 키 | null 값 | 메모 |
| HashMap | ✅ (정확히 1개) | ✅ (여러 개 가능) | get(k)가 null이면 “키 없음” 또는 “값이 null”일 수 있음 → containsKey(k)로 구분 필요 |
| Hashtable | ❌ | ❌ | put/get에 null 사용 시 NullPointerException |
| ConcurrentHashMap | ❌ | ❌ | 동시성·원자적 연산 일관성을 위해 null 금지 (compute/merge의 null 반환은 제거/미삽입 신호) |
왜 규칙이 다를까?
1) HashMap — 편의성 + 현대적 API
- containsKey가 있어 get 결과가 null이어도 존재 여부를 구분 가능
- 그래서 키 1개 null + 여러 null 값을 허용
2) Hashtable — 역사적 설계(모호성 회피)
- 초창기(레거시) 설계에서 get의 null은 “없음” 의미로 사용
- 값 자체에 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을 사용하세요.
'Java > POJO' 카테고리의 다른 글
| ConcurrentHashMap (0) | 2025.09.29 |
|---|---|
| 동기화 클래스 5편 - Executors, FutureTask, Future (0) | 2019.07.03 |
| 동기화 클래스 4편 - Exchanger (0) | 2019.07.03 |
| 동기화 클래스 3편 - CyclicBarrier (0) | 2019.07.03 |
| 동기화 클래스 2편 - CountDownLatch (0) | 2019.07.03 |