빠른 비교
Map<String, Integer> counts = new HashMap<>();
counts.put("java", counts.getOrDefault("java", 0) + 1);
counts.merge("java", 1, Integer::sum);
Map<Long, List<Order>> ordersByUser = new HashMap<>();
ordersByUser
.computeIfAbsent(userId, id -> new ArrayList<>())
.add(order);선택 기준
어떤 Map 메서드를 먼저 떠올리면 되나
| 상황 | 먼저 볼 메서드 |
|---|---|
| 없을 때 기본값만 읽기 | getOrDefault |
| 없을 때 값을 만들어 저장 | computeIfAbsent |
| 있을 때만 값 갱신 | computeIfPresent |
| 있든 없든 현재 값 기준으로 재계산 | compute |
| 누적, 합산, 충돌 병합 | merge |
getOrDefault: 없을 때 기본값으로 읽기
getOrDefault는 map을 바꾸지 않고 읽기 기본값만 제공합니다. 집계 계산을 할 때는 결과를 다시 put해야 하고, 존재하지 않는 key를 실제로 추가하지 않습니다.
Map<String, Integer> scores = new HashMap<>();
int kimScore = scores.getOrDefault("kim", 0);
// 읽기 기본값만 제공하므로 map에는 아직 "kim" key가 없음
System.out.println(scores.containsKey("kim")); // false
scores.put("kim", kimScore + 10);computeIfAbsent: 없을 때만 생성해서 저장
computeIfAbsent는 캐시, 그룹핑, key별 리스트 누적에 잘 맞습니다. key가 없거나 값이 null이면 mapping function을 실행하고, 반환값이 null이 아니면 map에 저장합니다.
Map<Long, List<Order>> ordersByUser = new HashMap<>();
for (Order order : orders) {
ordersByUser
.computeIfAbsent(order.userId(), id -> new ArrayList<>())
.add(order);
}
Map<String, Pattern> patternCache = new HashMap<>();
Pattern pattern = patternCache.computeIfAbsent(
regex,
key -> Pattern.compile(key)
);merge: 누적과 충돌 정책을 한 줄로
merge(key, value, remappingFunction)은 key가 없으면 새 값을 넣고, key가 있으면 기존 값과 새 값을 합쳐 저장합니다. 빈도 계산, 합계 누적, 중복 key 충돌 정책에 적합합니다.
Map<String, Integer> wordCounts = new HashMap<>();
for (String word : words) {
wordCounts.merge(word, 1, Integer::sum);
}
Map<Long, User> usersById = new HashMap<>();
for (User user : users) {
usersById.merge(
user.id(),
user,
(oldUser, newUser) -> newUser.updatedAt().isAfter(oldUser.updatedAt())
? newUser
: oldUser
);
}체크포인트
| 목적 | 패턴 |
|---|---|
| 읽을 때만 기본값 사용 | map.getOrDefault(key, defaultValue) |
| key별 리스트 만들기 | map.computeIfAbsent(key, k -> new ArrayList<>()).add(value) |
| 숫자 누적 | map.merge(key, delta, Integer::sum) |
| null 반환 시 mapping 제거 | compute, computeIfPresent, merge의 remapping 결과 확인 |
| 동시 접근 map | ConcurrentHashMap의 원자성 보장 범위 확인 |
주의사항
getOrDefault는 값을 저장하지 않습니다. 기본값을 읽은 뒤 map에 key가 생겼다고 착각하면 이후 containsKey나 반복 처리에서 결과가 달라집니다.
Map<String, Integer> counts = new HashMap<>();
int count = counts.getOrDefault("missing", 0);
System.out.println(count); // 0
System.out.println(counts.containsKey("missing")); // falsemerge와 compute 계열에서 remapping 함수가 null을 반환하면 mapping이 제거될 수 있습니다. 삭제 의도가 아니라면 반환값이 null이 되지 않도록 분기를 명확히 두세요.
Map<String, Integer> counts = new HashMap<>();
counts.put("java", 1);
counts.merge("java", 1, (oldValue, newValue) -> null);
System.out.println(counts.containsKey("java")); // false참고 링크
2 sources