스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 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에는 남학생 두 명이 저장되어 있는 것을 볼 수 있다.