본문 바로가기
인프런/김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍

[인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 9. 스트림 API 3 - 컬렉터

by hxxyeoniii 2025. 6. 23.

컬렉터

스트림이 최종 연산 데이터를 처리할 때, 그 결과물을 리스트나 맵 같은 자료 구조에 담고 싶다면?

-> 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(...)...)