728x90
요약:Collectors.groupingBy는 각 요소에서 키를 뽑아, 같은 키끼리 리스트로 묶어Map을 만드는 수집기입니다. SQL의GROUP BY를 자바 코드로 옮긴 것으로, "카테고리별 상품", "부서별 직원", "길이별 단어"처럼 분류 가 필요한 거의 모든 실무·코테 문제의 핵심입니다. 이 글에서 기본 사용법, 반환Map의 타입 지정, 그리고 다운스트림 수집기로 가는 길목을 정리합니다.
파트2 중급에서 가장 자주 쓰는 수집기 하나만 고르라면 단연 groupingBy입니다. 데이터를 다루다 보면 "어떤 기준으로 묶어라"가 끝없이 나옵니다. for 루프로는 Map<K, List<V>>를 만들어 computeIfAbsent로 버킷을 꺼내고 add하는 번거로운 코드가 필요하지만, 스트림은 groupingBy(키추출) 한 줄입니다.
기본형은 키를 뽑는 함수 하나만 넘기면 됩니다. 그러면 Map<키, List<원소>> 가 나오죠. 여기서 한 걸음 더 나아가 "그룹별로 개수를 세고(17편), 합계를 내고(18편)" 하려면 다운스트림 수집기를 두 번째 인자로 넘깁니다. 이번 편은 그 출발점인 기본 그룹핑을 확실히 잡습니다.

문제 정의
문제. 단어 리스트를 글자 수(length) 기준으로 그룹핑하라.
- 입력:
List<String> words = ["java", "api", "stream", "lap", "go", "map"] - 기대 출력:
{2=[go], 3=[api, lap, map], 4=[java], 6=[stream]}
전통 방식(for-loop) — 그리고 스트림이 나은 이유
import java.util.*;
public class Traditional {
public static void main(String[] args) {
List<String> words = List.of("java", "api", "stream", "lap", "go", "map");
Map<Integer, List<String>> byLength = new HashMap<>();
for (String w : words) {
// 키가 없으면 새 리스트 생성, 있으면 기존 리스트에 추가
byLength.computeIfAbsent(w.length(), k -> new ArrayList<>()).add(w);
}
System.out.println(byLength);
}
}
computeIfAbsent로 "버킷이 없으면 만들고, 있으면 꺼내 추가"하는 전형적인 그룹핑 패턴입니다. 틀린 코드는 아니지만 매번 반복해 짜야 하고 실수(널 처리, 잘못된 키)도 잦습니다. groupingBy는 이 패턴을 표준 수집기로 만들어, 키 추출 함수 하나로 끝냅니다.
스트림 해법 — groupingBy

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class GroupingByExample {
public static void main(String[] args) {
List<String> words = List.of("java", "api", "stream", "lap", "go", "map");
// ① 기본 — 길이별로 묶기 (Map<Integer, List<String>>)
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println(byLength);
// ② 키 정렬이 필요하면 Map 구현을 TreeMap으로 지정
Map<Integer, List<String>> sorted = words.stream()
.collect(Collectors.groupingBy(
String::length, TreeMap::new, Collectors.toList()));
System.out.println(sorted);
}
}
한 줄씩 설명
groupingBy(String::length)— 각 단어에서length()를 키로 뽑고, 같은 키의 단어들을List로 모아Map<Integer, List<String>>을 만듭니다. 기본 반환 맵은HashMap입니다.groupingBy(키, TreeMap::new, toList())— 3-인자 버전입니다. 가운데TreeMap::new는 맵 공급자(mapFactory), 마지막toList()는 다운스트림 수집기입니다.TreeMap을 지정하면 키가 오름차순 정렬됩니다.- 다운스트림 자리에
counting()·summingInt(...)등을 넣으면 "그룹별 개수/합계"가 됩니다(17·18편).
실행 결과
{2=[go], 3=[api, lap, map], 4=[java], 6=[stream]}
{2=[go], 3=[api, lap, map], 4=[java], 6=[stream]}
변형·함정·주의
- ⚠ 기본 맵은
HashMap— 키 순서를 보장하지 않는다. 위 예시는 정수 키라 우연히 정렬돼 보이지만, 일반적으로 순서는 미보장입니다. 순서가 필요하면TreeMap(정렬)·LinkedHashMap(삽입순)을 3-인자 버전으로 지정하세요. - 그룹의 자료구조도 바꿀 수 있다. 다운스트림에
Collectors.toSet()을 주면 그룹이Set이 되어 중복이 제거됩니다.toCollection(TreeSet::new)로 정렬된 그룹도 가능합니다. - 빈 그룹은 생기지 않는다. 데이터에 없는 키는 맵에 아예 안 들어갑니다. "모든 분류를 0부터 보장"하려면 별도 초기화가 필요합니다.
- 키는
equals/hashCode기반. 객체를 키로 쓸 때는 두 메서드가 올바로 정의돼 있어야 그룹핑이 의도대로 됩니다(record는 자동 제공).
관련 면접 질문
- Q1.
groupingBy의 반환 타입과 기본 맵 구현은?
→ Map<K, List<T>>이며 기본 구현은 HashMap입니다. 따라서 키 순서는 보장되지 않고, 정렬·삽입순이 필요하면 3-인자 버전으로 TreeMap/LinkedHashMap을 지정합니다.
- Q2. 그룹별로 "리스트"가 아니라 "개수"가 필요하면?
→ 2-인자 버전에 다운스트림 수집기 Collectors.counting()을 넘깁니다: groupingBy(키, counting()) → Map<K, Long>.
시리즈 네비게이션
- ⬆️ 📎 목차 허브 — 자바 스트림 코딩 문제 41선 총정리 (0편으로 돌아가기)
- ⬅️ 📎 이전 편 — [자바 스트림 12] reduce로 누적 계산
- ➡️ 📎 다음 편 — [자바 스트림 14] partitioningBy로 이분 분할 (발행 시 연결)
- 📎 함께 읽기 — Java 25 LTS 새 기능 총정리 (최신 자바 문법 레퍼런스)
그룹핑이 "여러 키로 N개 묶기"였다면, 다음 편은 참/거짓 딱 두 그룹으로 나누는 partitioningBy입니다. 조건 하나로 데이터를 둘로 가르는 특수 그룹핑이죠.'쿤즈 Dev > Java' 카테고리의 다른 글
| [자바 스트림 12] reduce로 누적 계산 — 집계 로직을 직접 설계한다 (0) | 2026.10.28 |
|---|---|
| [자바 스트림 11] flatMap으로 중첩 평탄화 — 리스트 속 리스트를 한 줄로 (0) | 2026.10.27 |
| [자바 스트림 10] min·max 최솟값·최댓값 — 기준 하나로 극값 뽑기 (0) | 2026.10.26 |
| 자바 스트림, 이렇게 쓰면 느려진다 — 성능 함정 8가지와 개선법 (Java 21) (0) | 2026.10.25 |
| [자바 스트림 9] limit·skip 페이징 — 앞에서 자르고 건너뛰기 (0) | 2026.10.25 |
댓글