빠른 비교
Map<String, List<Student>> byDepartment =
students.stream()
.collect(Collectors.groupingBy(Student::getDepartment));갈리는 기준
어떤 수집기를 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 결과를 그냥 리스트로 모으기 | .toList() 또는 Collectors.toList() |
| 문자열로 이어 붙이기 | Collectors.joining(...) |
| 키 기준으로 그룹화 | Collectors.groupingBy(...) |
true/false 두 그룹으로 나누기 | Collectors.partitioningBy(...) |
| key-value 맵 만들기 | Collectors.toMap(...) |
| 그룹별 추가 집계 | groupingBy(..., downstream) |
Collectors: 수집 구조를 결정하는 terminal 연산
collect()는 Stream의 terminal 연산 중 가장 유연한 형태입니다. 단순히 Stream을 끝내는 것이 아니라, 결과를 어떤 자료구조로 모을지를 Collector로 지정합니다. toList, toSet, joining, counting 등 다양한 Collector가 제공됩니다.
// 리스트로 수집
List<String> names = users.stream()
.map(User::getName)
.collect(Collectors.toList()); // 또는 .toList()
// 문자열 연결
String csv = names.stream()
.collect(Collectors.joining(", "));groupingBy: 분류 기준으로 그룹화
groupingBy는 하나의 기준으로 요소를 분류해 Map<K, List<T>> 형태를 만듭니다. downstream collector를 추가하면 각 그룹을 다시 집계할 수 있습니다.
// 부서별 직원 목록
Map<String, List<Employee>> byDept =
employees.stream()
.collect(Collectors.groupingBy(Employee::getDept));
// 부서별 인원 수 (downstream: counting)
Map<String, Long> countByDept =
employees.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
Collectors.counting()));
// 부서별 평균 연봉
Map<String, Double> avgSalaryByDept =
employees.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
Collectors.averagingInt(Employee::getSalary)));toMap: key-value 쌍으로 수집할 때 충돌 주의
toMap은 각 요소를 key와 value로 변환해 Map을 만듭니다. key가 중복되면 기본적으로 IllegalStateException이 발생합니다. merge 함수의 두 파라미터는 (기존값, 새값) 순서입니다.
// ❌ key 중복 시 IllegalStateException!
Map<String, Employee> byName =
employees.stream()
.collect(Collectors.toMap(
Employee::getName,
e -> e)); // 동명이인이 있으면 예외 발생
// ✅ merge 함수: (기존값, 새값) → 반환할 값
Map<String, Employee> byName =
employees.stream()
.collect(Collectors.toMap(
Employee::getName,
e -> e,
(existing, duplicate) -> existing)); // 첫 번째 유지
// 부서별 최고 연봉 직원 — merge에서 비교 로직
Map<String, Employee> topByDept =
employees.stream()
.collect(Collectors.toMap(
Employee::getDept,
e -> e,
(a, b) -> a.getSalary() >= b.getSalary() ? a : b));partitioningBy — boolean 기준으로 두 그룹으로 나누기
partitioningBy는 groupingBy의 특수 케이스로 조건에 맞는 것(true)과 아닌 것(false) 두 그룹으로 나눕니다. 항상 Map<Boolean, List<T>>를 반환합니다.
// 합격/불합격 분리
Map<Boolean, List<Student>> result =
students.stream()
.collect(Collectors.partitioningBy(s -> s.getScore() >= 60));
List<Student> passed = result.get(true);
List<Student> failed = result.get(false);groupingBy vs toMap
같은 key에 여러 원소가 자연스럽게 모이는 문제라면 groupingBy가 먼저입니다. 같은 key당 결과를 하나만 남겨야 할 때만 toMap과 merge 함수를 떠올리면 됩니다.
// ✅ 부서별 여러 직원이 생기는 문제
Map<String, List<Employee>> byDept =
employees.stream()
.collect(Collectors.groupingBy(Employee::getDept));
// ✅ 부서별 대표 직원 하나만 남기는 문제
Map<String, Employee> leaderByDept =
employees.stream()
.collect(Collectors.toMap(
Employee::getDept,
e -> e,
(a, b) -> a.getLevel() >= b.getLevel() ? a : b));선택 기준
| 상황 | 적합한 선택 |
|---|---|
| 리스트로 수집 | .toList() 또는 Collectors.toList() |
| 중복 없이 수집 | Collectors.toSet() |
| 기준으로 그룹화 | Collectors.groupingBy(...) |
| 그룹별 추가 집계 | groupingBy(..., downstream) |
| boolean 조건으로 두 그룹 | Collectors.partitioningBy(...) |
| key-value 맵 생성 | Collectors.toMap(...) — 충돌 시 merge 함수 필요 |
| merge 함수 파라미터 순서 | (기존값, 새값) → 반환값 |
주의할 점
toMap은 key가 중복되면 예외가 발생합니다. 중복 가능성이 있는 데이터라면 merge 함수를 지정하거나, 애초에 groupingBy가 더 맞는 문제인지 다시 보는 편이 안전합니다.
// ❌ 중복 key가 있으면 IllegalStateException 발생
Map<String, Integer> scoreMap = students.stream()
.collect(Collectors.toMap(
Student::getName, // key: 이름 (중복 가능)
Student::getScore // value: 점수
));
// ✅ merge 함수로 충돌 정책 명시
Map<String, Integer> scoreMap = students.stream()
.collect(Collectors.toMap(
Student::getName,
Student::getScore,
Integer::max // 중복 시 최고 점수 유지
));partitioningBy는 항상 key가 true/false 둘뿐입니다. 그룹 이름이 더 많아질 수 있는 문제에 억지로 쓰면 오히려 모델이 흐려집니다.
// ❌ 부서처럼 값 종류가 여러 개인데 partitioningBy 사용
Map<Boolean, List<Employee>> byDept =
employees.stream()
.collect(Collectors.partitioningBy(e -> e.getDept().equals("IT")));
// ✅ 그룹 이름이 여러 개면 groupingBy
Map<String, List<Employee>> byDept =
employees.stream()
.collect(Collectors.groupingBy(Employee::getDept));참고 링크
2 sources