필요한 요소만 컬렉션에 담고 요소들을 그룹핑하여 집계할 수 있습니다.
리턴 타입 | 메서드 | 인터페이스 |
R | collect(Collector<T,A,R> collector) | Stream |
매개값인 Collector(수집기)는 어떤 요소를 어떤 컬렉션에 수집할 것인지 결정합니다.
Collector 타입 파라미터 T는 요소이고 A는 누적기입니다. 그리고 R은 요소가 저장될 컬렉션입니다.
T 요소를 A누적기가 R에 저장한다는 의미입니다.
Collector 구현 객체는 다음과 같이 Collectors 클래스의 다양한 정적 메서드를 이용해서 얻을 수 있습니다.
리턴 타입 | 메서드 | 인터페이스 |
Collector<T, ?, List<T>> | toList() | T를 List에 저장 |
Collector<T, ?, Set<T>> | toSet() | T를 Set에 저장 |
Collector<T, ?, Collection<T>> | toCollection( Supplier<Collection<T>> ) |
T를 Supplier가 제공한 Collection에 저장 |
Collector<T, ?, Map<K,U>> | toMap( Function<T,K> keyMapper, Function<T,U> valueMapper) |
T를 K와 U로 매핑해서 K를 키로 U를 값으로 Map에 저장 |
Collector<T, ?, ConcurrentMap<K,U>> | toConcurrentMap( Function<T, K> keyMapper, Function<T, U> valueMapper) |
T를 K와 U로 매핑해서 K를 키로, U를 값으로 ConcurrentMap에 저장 |
리턴값 Collector를 보면 A(누적기)가 ?로 되어 있는데 이것은 Collector가 R(컬렉션)에 T(요소)를 저장하는 방법을 알고 있어 A가 필요없기 때문입니다.
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);
1) 전체 학생 List에서 Stream을 얻습니다.
2) 남학생만 필터링해서 Stream을 얻습니다.
3) List에 Student를 수집하는 Collector를 얻습니다.
4) Stream에서 collect() 메서드로 Student를 수집해 새로운 List를 얻습니다.
List<Student> maleList = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.MALE)
.collect(Collectors.toList());
1~4번의 코드를 간결화 하면 위 코드로 작성이 가능합니다.
1) Stream<Student> totalStream = totalList.stream();
2) Stream<Student> femaleStream = totalStream.filter(s->s.getSex() == Student.Sex.FEMALE);
3) Supplier<HashSet<Student>> supplier = HashSet :: new;
4) Collectors<Student, ?, HashSet<Student>> collector = Collectors.toCollection(supplier);
5) Set<Student> femaleSet = femaleStream.collect(collector);
위 코드는 전체 학생 중에서 여학생만 필터링해 별도 HashSet을 생성합니다.
1) 전체 학생 List에서 Stream을 얻습니다.
2) 여학생만 필터링해 Stream을 얻습니다
3) 새로운 HashSet 공급하는 Supplier를 얻습니다
4) Supplier가 공급하는 HashSet에 Student를 수집하는 Collector는 얻습니다
5) Stream에서 collect() 메서드로 Student를 수집해 새로운 HashSet을 얻습니다
위 과정을 간단히 하면 아래와 같이 작성됩니다.
Set<Student> femaleSet = totalList.stream()
.filter(s -> s.getSex() == Student.Sex.FEMALE)
.collect(Collectors.toCollection(HashSet :: new));
사용자 정의 컨테이너 수집하기
List, Set, Map 같은 컬렉션이 아닌 사용자 정의 컨테이너 객체에 수집하는 방법입니다.
인터페이스 | 리턴타입 | 메서드 |
Stream | R | collect(Supplier<R>, BiConsumer<R, ?, super T>, BiConsumer<R, R>) |
IntStream | R | collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer<R,R>) |
DoubleStream | R | collect(Supplier<R>, ObjDoubleConsumer<R>, BiConsumer<R,R>) |
1) 첫번째 Supllier는 요소들이 수집될 컨테이너 객체(R)를 생성하는 역할입니다.
순차 처리(싱글 스레드) 스트림에서는 단 한 번 Supplier가 실행되고 하나의 컨테이너 객체를 생성합니다.
병렬 처리(멀티 스레드) 스트림에서는 여러 Supplier가 실행되고 스레드별 여러 컨테이너 객체가 생성됩니다.
하지만 최종적으로 하나의 컨테이너 객체로 결합됩니다.
2) 두번째 XXXConsumer는 컨테이너 객체(R)에 요소(T)를 수집하는 역할을 합니다. 스트림에서 요소를 컨테이너에 수집할 때마다 XXXConsumer 가 실행됩니다.
3) 세번째 BiConsumer는 컨테이너 객체(R)를 결합하는 역할을 합니다. 순차 처리 스트림에서는 호출되지 않고 병렬 처리 스트림에서만 호출되어 스레드별 생성된 컨테이너 객체를 결합해 최종 컨테이너 객체를 완성합니다.
순차처리를 이용해 사용자 정의 객체에 요소를 수집하는 케이스
public class MainStudent {
private List<Student> list; // 요소를 저장할 컬렉션
public MaleStudent() {
list = new ArrayList<Student>();
sout(Thread.currentThread().getName()) // 생성자를 호출하는 스레드이름
}
public void accumulate(Student student) { // 요소를 수집할 메서드
list.add(student);
sout(Tread.currentThread().getName())
}
public void combine(MaleStudent other) { // 두 MaleStudent를 결합하는 메서드 (병렬 처리시만 호출)
list.addAll(other.getList());
sout(Thread.currentThread().getName())
}
public List<Student> getList() { // 요소가 저장될 컬렉션을 리턴
return list;
}
}
생성자가 몇 번 호출되었는지 확인, 순차처리 스트림에서 MaleStudent() 생성자는 딱 한번 호출 되고 하나의 MaleStudent 객체만 생성됩니다.
순차처리 스트림에서 MaleStudent() 생성자는 한번만 호출되고 하나의 MaleStudent 객체만 생성됩니다. accumulate() 메서드는 매개값으로 받은 Student를 list 필드에 수집하는데 accumulate() 메서드가 몇번 실행되었는지 확인하기 위해 호출한 스레드의 이름과 함께 메서드 이름 확인.
Stream<Student> totalStream = totalList.stream();
Stream<Student> maleStream = totalStream.filter(s -> s.getSex() == Student.Sex.MALE);
Supplier<MaleStudent> supplier = () -> new MaleStudent();
BiConsumer<MaleStudent, Student> accumulator = (ms, s) -> ms.accumulate(s);
BiConsumer<MaleStudent, MaleStudent> combiner = (ms1, ms2) -> ms1.combine(ms2);
MaleStudent maleStudent = maleStream.collect(supplier, accumulator, combiner);
1) 전체 학생 리스트 Stream을 얻는다
2) 남학생 리스트 Stream을 얻는다
3) MaleStudent 공급 Supplier을 얻는다
4) MaleSudent와 Student를 매개값으로 받아 MaleStudent의 accumulate() 메서드로 Student를 수집하는 BiConsumer를 얻는다
5) 두 개의 MaleStudent를 매개값으로 받아 combine() 메서드로 결합하는 BiConsumer를 얻는다.
6) supplier가 제공하는 MaleStudent에 accumulator가 Student를 수집해서 최종 처리된 MaleStudent를 얻는다.
싱글 스레드에서는 combiner는 사용되지 않는다
MaleStudent maleStudent = totalList.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, MaleStuddent :: combine);
람다식을 이용해 작성
자바 Observable (publisher, subscriber) (0) | 2024.02.26 |
---|---|
스트림과 병렬 처리 (0) | 2024.02.21 |
요소 그룹핑, 집계 Collector groupingByConcurrent (1) | 2024.02.13 |
컬렉션 Comparable, Comparator 인터페이스 구현 (0) | 2024.02.12 |
검색 기능에 좋은 컬렉션 TreeSet, TreeMap (1) | 2024.02.11 |
댓글 영역