스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공하고 있다.
Stream의
collect(Collector<T, A, R> collect)메소드는
필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다.

매개값인 Collector(수집기)는 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.
Collectors의 타입 파라미터 T는 요소, A는 누적기, R은 요소가 저장될 컬렉션을 의미한다.
즉, Collector<T, A, R>은 요소를 A누적기가 R에 저장한다는 의미이다.
Collector의 구현 객체는 다음과 같이 Collectors 클래스의 다양한 정적 메소드를 이용해서 얻을 수 있다.

?로 되어 있는 것은 Collector가 R(컬렉션)에 T(요소)를 저장하는 방법을 알고 있어 A(누적기)가 필요 없음을 의미한다.전체 학생 리스트 중에서 남학생들만 필터링해서 별도의 List로 생성하고 싶을 때, 다음의 코드를 이용하면 된다.
1. Stream<Student> totalStream = totalList.stream();
2. Stream<Student> maleStream = totalStream.filter(s -> s.getSex() == Student.Sex.MALE);
3. Collector<Student, ?, List<Student>> collector = Collectors.toList();
4. List<Student> maleList = maleStream.collect(collector);
위의 코드에서 변수를 생략하면 다음과 같이 간단하게 작성할 수 있다.
Set<Student> femaleSet = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.FEMALE)
.collect(Collectors.toCollection(HashSet::new));
전체 코드
public class Student {
public enum Sex {MALE, FEMALE}
public enum City {Sedoul, Incheon}
private String name;
private int score;
private Sex sex;
private City city;
public Student(String name, int score, Sex sex) {
this.name = name;
this.score = score;
this.sex = sex;
}
public Student(String name, int score, Sex sex, City city) {
this.name = name;
this.score = score;
this.sex = sex;
this.city = city;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public Sex getSex() {
return sex;
}
public City getCity() {
return city;
}
}
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class ToListExample {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("김철수", 10, Student.Sex.MALE),
new Student("이영희", 6, Student.Sex.FEMALE),
new Student("박철수", 10, Student.Sex.MALE),
new Student("최영희", 6, Student.Sex.FEMALE)
);
//남학생들만 묶어서 Lit 생성
List<Student> maleList = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.MALE)
.collect(Collectors.toList());
maleList.stream()
.forEach(s -> System.out.println(s.getName()));
System.out.println();
//여학생들만 묶어서 HashSet 생성
Set<Student> femaleSet = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.FEMALE)
.collect(Collectors.toCollection(HashSet::new));
femaleSet.stream()
.forEach(s -> System.out.println(s.getName()));
}
}
김철수
박철수
이영희
최영희
사용자 정의 컨테이너 객체에 수집하는 방법에 대해 알아보자.
스트림은 요소들을 필터링, 또는 매핑해서 사용자 정의 컨테이너 객체에 수집할 수 있도록 다음과 같이 collect() 메소드를 추가적으로 제공한다.

Supplier는 요소들이 수집될 컨테이너 객체(R)를 생성하는 역할을 한다. Supplier가 실행되고 하나의 컨테이너 객체를 생성한다.Supplier가 실행되고 스레드별로 여러 개의 컨테이너 객체를 생성한다.XXXConsumer는 컨테이너 객체(R)에 요소(T)를 수집하는 역할을 한다. XXXConsumer가 실행된다.BiConsumer는 컨테이너 객체(R)를 결합하는 역할을 한다. R은 요소들이 최종 수집된 객체이다. 학생들 중에서 남학생만 수집하는 MaleStudent 컨테이너가 다음과 같이 정의되어 있다고 가정해보자.
import java.util.ArrayList;
import java.util.List;
public class MaleStudent {
private List<Student> list; //요소를 저장할 컬렉션
public MaleStudent() {
list = new ArrayList<Student>();
System.out.println("[" + Thread.currentThread().getName() + "] MaleStudent()");
}
public void accumulate(Student student) { //요소를 수집하는 메소드
list.add(student);
System.out.println("[" + Thread.currentThread().getName() + "] accumulate");
}
public void combine(MaleStudent other) { //두 MaleStudent를 결합하는 메소드 (병렬 처리 시에만 호출)
list.addAll(other.getList());
System.out.println("[" + Thread.currentThread().getName() + "] combine()");
}
public List<Student> getList() { //요소가 저장된 컬렉션을 리턴
return list;
}
}
스트림에서 읽은 남학생을 MaleStudent에 수집하는 코드는 다음과 같다.
1. Stream<Student> totalStream = totalList.stream();
2. Stream<Student> maleStream = totalStream.filter(s -> s.getSex() == Student.Sex.MALE);
3. Supplier<MaleStudent> supplier = () -> new MaleStudent();
4. BiConsumer<MaleStudent, Student> accumulator = (ms, s) -> ms.accumulate(s);
5. BiConsumer<MaleStudent, MaleStudent> combiner = (ms1, ms2) -> ms1.combine(ms2);
6. MaleStudent maleStudent = maleStream.collect(supplier, accumulator, combiner);
accumulate() 메소드로combine() 메소드로 결합하는 BiConsumer를 얻는다.참고 | 싱글 스레드에서는 combiner가 사용되지 않는다.
위의 코드에서 변수를 생략하면 다음과 같이 간단하게 작성할 수 있다.
MaleStudent maleStudent = totalSList.stream()
.filter(s -> s.getSex() == Student.Sex.MALE)
.collect(
() -> new MaleStudent(),
(r, t) -> r.accumulate(t),
(r1, r2) -> r1.combine(r2)
);
람다식을 메소드 참조로 변경하면 다음과 같이 더욱 간단하게 작성할 수 있다.
MaleStudent maleStudent = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.MALE)
.collect(MaleStudent::new, MaleStudent::accumulate, MaleStudent::combine);
import java.util.Arrays;
import java.util.List;
public class MaleStudentExample {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("김철수", 10, Student.Sex.MALE),
new Student("이영희", 6, Student.Sex.FEMALE),
new Student("박철수", 10, Student.Sex.MALE),
new Student("최영희", 6, Student.Sex.FEMALE)
);
MaleStudent maleStudent = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.MALE)
.collect(MaleStudent::new, MaleStudent::accumulate, MaleStudent::combine);
maleStudent.getList().stream()
.forEach(s -> System.out.println(s.getName()));
}
}
[main] MaleStudent()
[main] accumulate
[main] accumulate
김철수
박철수
MaleStudent() 생성자가 딱 한 번 호출되었기 때문에 한 개의 MaleStudent 객체가 생성되었으며, accumulate()는 두 번 호출되었기 때문에 요소들이 2번 수집되었다.collect()가 리턴한 최종 MaleStudent에는 남학생 두 명이 저장되어 있는 것을 볼 수 있다.