프로그래밍 노트/JAVA

[JAVA] Stream 사용자 정의 컨테이너에 수집하기(collect())

깡냉쓰 2020. 1. 7. 22:29
728x90
반응형

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);
728x90
반응형