ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

메서드 (Method)

도입

조회, 조건부 수정, 계산형 업데이트 그리고 순회용 뷰를 상황에 맞게 고르는 것이다.

Map은 자바 컬렉션에서 가장 자주 쓰이는 인터페이스 중 하나이지만, 메서드가 많아 어려움을 겪습니다.

메서드들의 의미를 섞어 쓰면 null 처리, 조건 누락, 동시성 오해, 순서 착각 때문에 버그가 생기기 쉽습니다

단순히 값을 넣고 꺼내는 수준을 넘어서 메서드 역할이 매우 다양합니다.

getOrDefault, putIfAbsent, computeIfAbsent, computeIfPresent, compute, merge, entrySet, forEach, Map.of 계열을 이해하고 있으면 코드가 훨씬 짧고 안전해집니다.

필요성

null 체크, 중복 분기, 빈도 누적, 그룹핑을 통해 짧고 안전하게 만들 수 있습니다.

Map을 잘못 쓰면 if (map.containsKey(...))get()put()를 여러 번 섞어 써야 해서 코드가 길어집니다.

하지만 자바는 이런 패턴을 줄여 주는 메서드를 충분히 제공하고 있습니다.

 

✅  메서드가 필요한 여러 상황들

  • ✔️ 키 존재 여부 확인
  • ✔️ 없으면 기본값으로 처리
  • ✔️ 없을 때만 삽입
  • ✔️ 기존 값이 있을 때만 수정
  • ✔️ 빈도 수 누적
  • ✔️ Map<K, List<V>> 형태 그룹핑
  • ✔️ 키 존재 여부 확인
  • ✔️ 정렬된 키 탐색
  • ✔️ 수정 불가 Map 생성

먼저 알아둘 기본 규칙

메서드를 외우기 전에 get의 null 의미, 순서 보장 여부, 구현체 차이부터 먼저 구분해야 Map이 덜 헷갈린다
구현체 언제 쓰나 메서드 해석에서 주의할 점
HashMap 대부분의 일반적인 경우 반복 순서를 믿으면 안 된다
LinkedHashMap 입력 순서나 접근 순서가 중요할 때 같은 get 계열도 순서에 영향을 줄 수 있다
TreeMap 정렬된 키와 근접 탐색이 필요할 때 Map 기본 메서드 외에 NavigableMap 메서드까지 같이 봐야 한다
ConcurrentHashMap 멀티스레드에서 원자적 갱신이 필요할 때 HashMap의 compute 계열과 동시성 의미가 다르다
실전 팁
get()은 키가 없을 때도 null을 반환할 수 있고, 값 자체가 null이어도 null을 반환할 수 있습니다. 그래서 “없다”와 “null이 들어 있다”를 구분해야 하는 순간에는 containsKey()를 함께 보는 습관이 중요합니다.

조회 메서드

Map 메서드의 출발점은 get, containsKey, containsValue, size, isEmpty처럼 현재 상태를 읽는 메서드들이다
Map<String, Integer> score = new HashMap<>();

Integer v1 = score.get("kim");
boolean hasKim = score.containsKey("kim");
boolean has100 = score.containsValue(100);
int n = score.size();
boolean empty = score.isEmpty();

get()은 가장 기본적인 조회 메서드지만, 반환값만으로는 키가 없어서 null인지, 실제 값이 null인지 구분되지 않을 수 있습니다. 반면 containsKey()는 키 존재 여부 자체를 정확히 확인할 때 적합합니다.

언제 떠올리면 좋은가
  • 단순히 값을 꺼낼 때 → get()
  • 키 존재 여부가 중요할 때 → containsKey()
  • Map이 비었는지 빠르게 확인할 때 → isEmpty()
  • 엔트리 개수가 필요한 때 → size()

추가 / 삭제 / 덮어쓰기 메서드

put, putAll, remove, clear, replace는 Map의 구조를 직접 바꾸는 가장 기본적인 수정 메서드다
Map<String, Integer> map = new HashMap<>();

map.put("apple", 3);          // 추가 또는 덮어쓰기
map.put("apple", 5);          // 기존 값 3이 5로 교체됨
map.putAll(Map.of("banana", 2, "melon", 7));

map.remove("banana");         // 키로 삭제
map.replace("apple", 10);     // 키가 있을 때만 값 교체
map.clear();                  // 전체 비우기

put()은 가장 많이 쓰이지만, 항상 덮어쓴다는 점을 잊기 쉽습니다. 즉, 같은 키에 다시 put()하면 기존 값이 교체됩니다. replace()는 반대로 키가 이미 존재할 때만 값을 바꾸는 용도로 더 의도가 명확합니다.

실전 팁
put()의 의도가 “무조건 저장”인지, replace()의 의도가 “이미 있을 때만 수정”인지 구분해서 쓰면 코드 해석이 훨씬 쉬워집니다.

조건부 업데이트 메서드

getOrDefault, putIfAbsent, remove(key,value), replace(key,old,new)는 “조건이 맞을 때만 바꾸는” 패턴을 훨씬 안전하게 만든다
Map<String, Integer> stock = new HashMap<>();

int apple = stock.getOrDefault("apple", 0);   // 없으면 0
stock.putIfAbsent("apple", 10);               // 없을 때만 저장

stock.remove("apple", 5);                     // 값이 정확히 5일 때만 삭제
stock.replace("apple", 10, 20);               // 값이 정확히 10일 때만 20으로 변경

getOrDefault()는 “없으면 기본값으로 처리” 패턴을 짧게 만들고, putIfAbsent()는 “없을 때만 초기값 넣기” 패턴을 정리해 줍니다. remove(key, value)replace(key, oldValue, newValue)는 현재 값까지 확인한 뒤 수정할 수 있어서, 의도하지 않은 덮어쓰기를 막는 데 유용합니다.

자주 쓰는 상황
  • 설정값이 없으면 기본값 사용 → getOrDefault()
  • 초기값 한 번만 넣기 → putIfAbsent()
  • 기존 값이 기대와 같을 때만 교체 → replace(k, old, new)
  • 정확히 일치하는 매핑만 지우기 → remove(k, v)

계산형 메서드

computeIfAbsent, computeIfPresent, compute, merge, replaceAll은 Map 메서드 중에서도 가장 강력하지만 가장 의미를 헷갈리기 쉬운 축이다

이 메서드들은 단순 조회나 단순 저장이 아니라, 현재 값 상태를 읽고 그에 따라 새 값을 계산해 넣는 패턴을 위한 메서드입니다. 코드는 짧아지지만, 각 메서드의 조건과 null 처리 규칙을 정확히 알아야 합니다.

// 1) 없으면 생성
Map<String, List<String>> group = new HashMap<>();
group.computeIfAbsent("fruit", k -> new ArrayList<>()).add("apple");

// 2) 있을 때만 갱신
Map<String, Integer> count = new HashMap<>();
count.put("apple", 3);
count.computeIfPresent("apple", (k, v) -> v + 1);

// 3) 있든 없든 계산
count.compute("banana", (k, v) -> v == null ? 1 : v + 1);

// 4) 누적 병합
count.merge("apple", 1, Integer::sum);

// 5) 전체 값 일괄 변환
count.replaceAll((k, v) -> v * 2);
메서드별 감각 정리
  • computeIfAbsent → 없을 때만 생성하고 넣기
  • computeIfPresent → 있을 때만 다시 계산하기
  • compute → 있든 없든 무조건 계산 로직 적용하기
  • merge → 카운팅, 누적, 문자열 이어붙이기처럼 기존 값과 새 값을 합치기
  • replaceAll → 모든 엔트리 값을 일괄 변환하기
실전 팁
merge()빈도 수 누적에서 가장 많이 쓰이고, computeIfAbsent()그룹핑이나 Map<K, List<V>> 초기화에서 거의 정답처럼 쓰입니다. 반면 compute()는 가장 일반적인 대신 의미가 넓어서, 더 구체적인 메서드로 표현 가능한 상황이라면 merge()computeIfAbsent()가 더 읽기 쉬운 경우가 많습니다.

순회와 뷰 메서드

keySet, values, entrySet, forEach는 Map의 내용을 읽고 순회하는 핵심 도구이며, 반환 객체가 복사본이 아니라 연결된 뷰라는 점이 중요하다
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);

// key만 볼 때
for (String key : map.keySet()) {
    System.out.println(key);
}

// value만 볼 때
for (Integer value : map.values()) {
    System.out.println(value);
}

// key와 value를 함께 볼 때
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

// 람다 순회
map.forEach((k, v) -> System.out.println(k + " = " + v));

keySet(), values(), entrySet()은 별도의 복사 컬렉션이 아니라 원본 Map에 연결된 뷰입니다. 그래서 이 뷰를 통해 제거하면 원본 Map도 바뀌고, 반대로 원본 Map을 바꾸면 뷰 결과도 같이 달라집니다.

실전 선택 기준
  • 키만 필요하면 → keySet()
  • 값만 필요하면 → values()
  • 키와 값 둘 다 필요하면 → entrySet() 또는 forEach()
실전 팁
키와 값을 둘 다 써야 하는데 for (K key : map.keySet()) map.get(key)처럼 돌기보다, entrySet()으로 직접 순회하는 편이 더 의도가 분명합니다.

정렬 / 탐색 메서드

정렬된 키가 필요하면 Map 기본 메서드만 보지 말고 TreeMap과 NavigableMap 메서드까지 같이 봐야 해법이 깔끔해진다

기본 Map 인터페이스는 키 정렬이나 근접 탐색을 보장하지 않습니다. 하지만 TreeMap은 정렬된 키 구조를 제공하고, NavigableMap 메서드를 통해 가장 가까운 작은 키 / 큰 키를 바로 찾을 수 있습니다.

TreeMap<Integer, String> map = new TreeMap<>();

map.put(10, "A");
map.put(30, "B");
map.put(50, "C");

System.out.println(map.firstKey());       // 10
System.out.println(map.lastKey());        // 50
System.out.println(map.lowerEntry(30));   // 30보다 작은 가장 큰 키
System.out.println(map.floorEntry(30));   // 30 이하 중 가장 큰 키
System.out.println(map.ceilingEntry(30)); // 30 이상 중 가장 작은 키
System.out.println(map.higherEntry(30));  // 30보다 큰 가장 작은 키
관련 상황
  • 정렬된 출력이 필요할 때
  • 가장 가까운 이전 키 / 다음 키를 찾을 때
  • 범위 탐색이 필요할 때
  • 최솟값 / 최댓값 키를 바로 꺼내고 싶을 때

정적 팩터리 메서드

Map.of, Map.ofEntries, Map.copyOf는 작은 Map을 간결하게 만들거나 수정 불가 Map을 만들 때 매우 유용하다
Map<String, Integer> m1 = Map.of(
    "apple", 3,
    "banana", 5
);

Map<String, Integer> m2 = Map.ofEntries(
    Map.entry("apple", 3),
    Map.entry("banana", 5),
    Map.entry("melon", 7)
);

Map<String, Integer> original = new HashMap<>();
original.put("x", 1);
original.put("y", 2);

Map<String, Integer> readonly = Map.copyOf(original);

이 계열의 핵심은 간결함수정 불가성입니다. 즉, 설정값이나 고정 데이터처럼 이후 변경되면 안 되는 Map을 표현할 때 매우 적합합니다.

실전 팁
Map.of()로 만든 Map은 수정할 수 없습니다. 그래서 이후 put(), remove(), replace()를 하려는 코드라면 처음부터 HashMap으로 복사해 두는 편이 낫습니다.

자주 하는 실수

Java Map 메서드를 헷갈리는 이유는 대개 get의 null 의미, compute 계열의 삭제 규칙, 구현체별 순서 차이, 동시성 오해 때문이다
  • get() 결과가 null이면 무조건 키가 없다고 단정함 → 값 자체가 null일 수도 있음
  • containsValue()를 키 조회처럼 남용함 → 용도 자체가 다름
  • put()가 기존 값을 덮어쓴다는 점을 놓침
  • computeIfAbsent()merge()를 비슷하게 보고 섞어 씀
  • computeIfPresent(), compute(), merge()에서 함수 결과가 null이면 매핑이 제거될 수 있음을 놓침
  • HashMap에서 반복 순서를 기대함
  • Map.of()Map.copyOf()로 만든 맵에 수정 메서드를 호출함
  • putIfAbsent(), computeIfAbsent()를 썼다고 해서 일반 HashMap에서도 스레드 안전하다고 착각함
  • keySet(), values(), entrySet()을 복사본으로 착각함

선택 루틴

Map 메서드를 고를 때는 “지금 하고 싶은 것이 조회인지, 조건부 삽입인지, 누적인지, 정렬 탐색인지”부터 먼저 분류하는 습관이 좋다
  1. 단순 조회get(), containsKey()부터 본다.
  2. 없으면 기본값이면 getOrDefault()를 먼저 떠올린다.
  3. 없을 때만 넣기putIfAbsent()를 쓴다.
  4. 카운팅 / 누적이면 merge()를 먼저 의심한다.
  5. 그룹핑 / 리스트 초기화computeIfAbsent()가 가장 자주 맞는다.
  6. 기존 값이 있을 때만 갱신이면 computeIfPresent()replace()를 본다.
  7. 전체 값을 한 번에 변환하려면 replaceAll()을 본다.
  8. 정렬된 키 탐색이 필요하면 TreeMapNavigableMap 메서드로 넘어간다.
  9. 수정 불가 상수 Map이면 Map.of(), Map.ofEntries(), Map.copyOf()를 본다.

디버깅

Map 코드가 꼬일 때는 메서드 이름만 보지 말고, 현재 키 상태·null 의미·구현체 특성·수정 가능 여부를 먼저 점검해야 한다
1
현재 문제가 키가 없는 것인지, 값이 null인 것인지부터 구분한다.
2
덮어쓰기가 의도였는지, 조건부 업데이트가 의도였는지 다시 본다.
3
compute 계열이나 merge에서 null 반환이 삭제 의미로 해석되지는 않는지 확인한다.
4
반복 순서가 중요한데 HashMap을 쓰고 있지는 않은지 점검한다.
5
수정 불가 Map에 쓰기 메서드를 호출하고 있지 않은지 확인한다.
6
멀티스레드 환경이라면 일반 Map이 아니라 ConcurrentMap 계열이 필요한 상황인지 본다.

요약

Java Map 메서드는 단순 저장 도구가 아니라 조회, 조건부 수정, 계산형 갱신, 순회, 정렬 탐색, 불변 생성까지 아우르는 실전 핵심 도구다
  • ✅ 조회는 get(), 존재 여부는 containsKey()로 구분하는 습관이 중요하다.
  • ✅ 기본값은 getOrDefault(), 조건부 삽입은 putIfAbsent()가 핵심이다.
  • ✅ 그룹핑은 computeIfAbsent(), 누적 카운팅은 merge()가 가장 자주 맞는다.
  • keySet(), values(), entrySet()은 원본 Map에 연결된 뷰다.
  • ✅ 정렬과 근접 탐색은 TreeMap / NavigableMap 메서드까지 함께 봐야 한다.
  • ✅ 수정 불가 맵은 Map.of(), Map.ofEntries(), Map.copyOf()로 깔끔하게 만들 수 있다.
  • ✅ 동시성 환경에서는 일반 Map의 계산형 메서드를 원자적이라고 가정하면 안 된다.
728x90