컬렉터
스트림이 최종 연산 데이터를 처리할 때, 그 결과물을 리스트나 맵 같은 자료 구조에 담고 싶다면?
-> Collectors를 활용한다.
> collect 연산은 반환값을 만들어 내는 최종 연산
> collect(Collector<? super T, A, R> collector 형태를 주로 사용
Collectors의 주요 기능 표 정리

다운 스트림 컬렉터
그룹화된 이후, 각 그룹 내부에 추가적인 연산 또는 결과물(평균, 합계, 최댓값, 최솟값 등)을 정의하는 역할을 해줌
-> Collectors.groupBy(...) 또는 Collectors.partiioningBy(...)에서 두 번 째 인자로 전달되는 Collector를 다운 스트림 컬렉터라고 함
예시) 학생 객체를 학년 기준 groupBy한 후, 그룹화한 학년별 점수의 합을 구함

// 예시
Map<KeyType, DownstreamResult> result =
stream.collect(Collectors.groupingBy(
element -> 분류 기준 Key, // 1) groupingBy용 분류 함수
downstreamCollector // 2) 그룹 내부를 처리할 다운 스트림 컬렉터
));
-> downstreamCollector는 classifier에 의해 분류된 각 그룹 내부 요소들을 다시 한 번 어떻게 처리할지 정의하는 역할
-> 다운 스트림 컬렉터를 명시하지 않으면 기본적으로 Collectors.toList()가 적용됨
다운 스트림 컬렉터 종류

Student.java
package stream.collectors;
public class Student {
private String name;
private int grade;
private int score;
public Student(String name, int grade, int score) {
this.name = name;
this.grade = grade;
this.score = score;
}
public int getGrade() {
return grade;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", grade=" + grade +
", score=" + score +
'}';
}
}
DownStreamMain1.java
package stream.collectors;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class DownStreamMain1 {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("Kim", 1, 85),
new Student("Park", 1, 70),
new Student("Lee", 2, 70),
new Student("Han", 2, 90),
new Student("Hoon", 3, 90),
new Student("Ha", 3, 89)
);
// 1. 학년별로 학생들을 그룹화
// 다운스트림의 toList()는 생략 가능!
Map<Integer, List<Student>> collect1_1 = students.stream()
.collect(Collectors.groupingBy(
// s -> s.getGrade(),
Student::getGrade, // 그룹화의 기준 = 학년
Collectors.toList() // 다운스트림 : 리스트로 수집
));
System.out.println("collect1_1 : " + collect1_1);
// collect1_1 : {1=[Student{name='Kim', grade=1, score=85}, Student{name='Park', grade=1, score=70}],
// 2=[Student{name='Lee', grade=2, score=70}, Student{name='Han', grade=2, score=90}],
// 3=[Student{name='Hoon', grade=3, score=90}, Student{name='Ha', grade=3, score=89}]}
// 2. 학년별로 학생들의 이름 출력
Map<Integer, List<String>> collect2 = students.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.mapping(Student::getName, // 다운스트림 1: 학생 -> 이름 변환
Collectors.toList() // 다운스트림 2: 변환 값을 List로 수집
)));
System.out.println("collect2 : " + collect2);
// collect2 : {1=[Kim, Park], 2=[Lee, Han], 3=[Hoon, Ha]}
// 3. 학년별로 학생들의 수 출력
Map<Integer, Long> collect3 = students.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.counting()
));
System.out.println("collect3 : " + collect3);
// collect3 : {1=2, 2=2, 3=2}
// 4. 학년별로 평균 성적 출력
Map<Integer, Double> collect4 = students.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.averagingInt(s -> s.getScore())
));
System.out.println("collect4 : " + collect4);
// collect4 : {1=77.5, 2=80.0, 3=89.5}
}
}
DownStreamMain2.java
package stream.collectors;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class DownStreamMain2 {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("Kim", 1, 85),
new Student("Park", 1, 70),
new Student("Lee", 2, 70),
new Student("Han", 2, 90),
new Student("Hoon", 3, 90),
new Student("Ha", 3, 89)
);
// 1. 학년별로 학생 그룹화
Map<Integer, List<Student>> collect1 = students.stream()
.collect(Collectors.groupingBy(Student::getGrade));
System.out.println("collect1 : " + collect1);
// 2. 학년별 가장 점수가 높은 학생 구하기 -> reducing 사용
Map<Integer, Optional<Student>> collect2 = students.stream()
.collect(Collectors.groupingBy(Student::getGrade,
Collectors.reducing((s1, s2) -> s1.getScore() > s2.getScore() ? s1 : s2)));
System.out.println("collect2 : " + collect2);
// collect2 : {1=Optional[Student{name='Kim', grade=1, score=85}],
// 2=Optional[Student{name='Han', grade=2, score=90}],
// 3=Optional[Student{name='Hoon', grade=3, score=90}]}
// 3. 학년별 가장 점수가 높은 학생 구하기 -> maxBy 사용
Map<Integer, Optional<Student>> collect3 = students.stream().collect(
Collectors.groupingBy(
Student::getGrade,
// Collectors.maxBy((s1, s2) -> s1.getScore() > s2.getScore() ? 1 : -1)));
Collectors.maxBy(Comparator.comparingInt(Student::getScore))
));
System.out.println("collect3 : " + collect3);
// 4. 학년별 가장 접수가 높은 학생의 이름 구하기
// 학년별 그룹 -> 가장 점수가 높은 학생 -> 이름 구하기
Map<Integer, String> collect4 = students.stream().collect(
Collectors.groupingBy(
Student::getGrade,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(Student::getScore)),
sOpt -> sOpt.get().getName()
)
));
System.out.println("collect4 : " + collect4);
// collect4 : {1=Kim, 2=Han, 3=Hoon}
}
}
mapping() vs collectingAndThen()
1) mapping
: 그룹화된 각 그룹 내의 개별 요소들을 다른 값으로 변환한 뒤, 그 변환된 값들을 다시 다른 Collector로 수집할 수 있게 해줌
: ex) mapping(Student::getName, toList())
2) collectingAndThen()
: 다운 스트림 컬렉터가 최종 결과를 만든 뒤 한번 더 후처리할 수 있도록 해줌
: collectingAndThen(maxBy(...), optional -> optional.map(...)...)
'인프런 > 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍' 카테고리의 다른 글
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 11. 디폴트 메서드 (0) | 2025.06.30 |
|---|---|
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 10. Optional (1) | 2025.06.25 |
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 9. 스트림 API 2 - 기능 (0) | 2025.06.17 |
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 8. 스트림 API 1 - 기본 (3) | 2025.06.14 |
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 7. 메서드 참조 (0) | 2025.05.28 |