List, Set, Map과 같은 java collection이 아니라, 우리가 직접 만든 클래스로 컨테이너를 사용하려면 어떻게 해야 할까?
스트림은 사용자 정의 컨테이너 객체에 수집할 수 있도록 Stream 인터페이스는 collect() 메소드를 제공한다. (Stream, IntStream, LongStream, DoubleStream 인터페이스의 collect 메소드)
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
collect(Supplier<R>, BiConsumer<R,? super T>, BiConsumer<R,R>) (리턴타입 R)
첫 번째 매개변수, 공급자(Supplier<R>)
: 요소들이 수집될 컨테이너 객체(R)을 생성하는 역할을 한다. 순차 처리(싱글 스레드)스트림에서는 단 한 번 Supplier가 실행되며, 병렬 처리(멀티 스레드)스트림에서는 여러 번 Supplier가 실행되고 스레드별로 컨테이너 객체를 생성한다. (최종적으로는 하나의 컨테이너 객체로 결합)두 번째 매개변수, 누산자(XXXConsumer<R, ? super T>)
: 컨테이너 객체(R)에 요소(T)를 수집하는 역할을 함. 요소를 컨테이너에 수집할 때마다 XXXConsumer가 실행된다.세 번째 매개변수, 결합자(BiConsumer<R,R>)
: 컨테이너 객체(R)를 결합하는 역할을 하며, 순차 처리 스트림에서는 호출되지 않고, 병렬 처리 스트림에서만 호출되어 컨테이너 객체를 결합한다.
- 리턴 타입 R은 요소들이 최종 수집된 컨테이너 객체이다.
- 순차 처리 스트림에서는 리턴 객체가 첫 번째 Supplier가 생성한 객체이다.
글로된 설명보다는 코드를 보는게 이해가 쉬우니, 코드를 바로 보자.
Student 클래스
@Getter
@Setter
@ToString
public class Student {
@Getter
public enum Sex {MALE, FEMALE}
@Getter
public enum City {Seoul, Incheon}
public Student(String name, int score, Sex sex) {
this.name = name;
this.score = score;
this.sex = sex;
}
private String name;
private int score;
private Sex sex;
private City city;
}
남학생이 저장된 컨테이너 MalStudent 클래스
@Slf4j
@ToString
public class MaleStudent {
// 학생들 중 남학생만 수집한다.
private List<Student> list; // 요소를 저장할 컬렉션
public MaleStudent(){
list = new ArrayList<Student>();
log.info("Thread : [ {} ] MaleStudent()", Thread.currentThread().getName());
}
public void accumulate(Student student){ // 요소를 수집하는 메소드
list.add(student);
log.info("Thread : [ {} ] accumulate()", Thread.currentThread().getName());
}
public void combine(MaleStudent other){ // 두 MaleStudent를 결합하는 메소드(병렬시에만 호출)
list.addAll(other.getList());
log.info("Thread : [ {} ] combine()", Thread.currentThread().getName());
}
public List<Student> getList(){
return list;
}
}
남학생을 MaleStudent에 누적
public class MaleStudentMain1 {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("깡냉", 10, Student.Sex.MALE),
new Student("손생", 8, Student.Sex.MALE),
new Student("크리스챤", 5, Student.Sex.MALE),
new Student("원숭이", 5, Student.Sex.MALE),
new Student("완두콩", 0, Student.Sex.FEMALE),
new Student("완두콩2", 0, Student.Sex.FEMALE)
);
Stream<Student> totalStream = totalList.stream();
Stream<Student> maleStream = totalStream.filter(s -> s.getSex() == Student.Sex.MALE);
// 첫 번째 매개변수 공급자(Supplier)
Supplier<MaleStudent> supplier = () -> new MaleStudent();
// 두 번째 매개변수 누산자(XXXConsumer<R, ? super T>)
BiConsumer<MaleStudent, Student> accumulator = (ms, s) -> ms.accumulate(s);
// 세 번째 매개변수 결합자(BiConsumer<R,R>)
BiConsumer<MaleStudent, MaleStudent> combiner = (ms1, ms2) -> ms1.combine(ms2);
MaleStudent maleStudent = maleStream.collect(supplier, accumulator, combiner);
System.out.println(maleStudent.getList());
}
}
결과
Thread : [ main ] MaleStudent()
Thread : [ main ] accumulate()
Thread : [ main ] accumulate()
Thread : [ main ] accumulate()
Thread : [ main ] accumulate()
[Student(name=깡냉, score=10, sex=MALE, city=null), Student(name=손생, score=8, sex=MALE, city=null), Student(name=크리스챤, score=5, sex=MALE, city=null), Student(name=원숭이, score=5, sex=MALE, city=null)]
위에서 설명한 것과 같이 순차 처리일 경우 MaleStudent(Supplier)가 한번 호출되며,
accumulate는 요소를 컨테이너에 수집할때 마다 호출된 것을 볼 수 있다. (남학생이 4명이므로, 4번 호출되었다.)
위의 코드는 아래와 같이 좀 더 간단하게 표현할 수 있다.
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, MaleStudent::combine);
'프로그래밍 노트 > JAVA' 카테고리의 다른 글
[JAVA] JVM 가상머신 메모리 구조 (0) | 2020.12.19 |
---|---|
[JAVA] Stream 요소를 그룹핑해서 수집하기(Collectors.groupingBy) (0) | 2020.01.09 |
[JAVA] Stream 필터링한 요소 수집(collect()) (0) | 2020.01.07 |
[JAVA] Stream 매칭(allMatch(), anyMatch(), noneMatch()) (0) | 2020.01.07 |
[JAVA] Optional 사용해서 null-safety한 코드짜기 (0) | 2019.11.25 |