ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java - 스트림(Stream) 최종 처리 메서드의 종류와 사용 방법(2)
    Language/Java 2022. 12. 1. 13:58

    스트림(Stream) 최종 처리 메서드의 종류와 사용 방법(2)

    최종 처리 메서드의 종류

     

    지난 게시글에서는 수집을 제외한 최종 처리 메서드에 대해서 알아보았다. 이번 게시글에서는 수집을 사용한 최종 처리 메서드를 살펴보겠다.

     

    수집

    스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메서드인 collect() 메서드를 제공하고 있다. 이 메서드를 사용하면 필요한 요소만 컬렉션으로 담을 수 있고, 요소들을 그룹핑한 후 집계할 수 있다.

     

    필터링한 요소 수집
    public class Student {
        private String name;
        private Gender gender;
        private int score;
    
        public Student(String name, Gender gender, int score) {
            this.name = name;
            this.gender = gender;
            this.score = score;
        }
    
        public String getName() {
            return name;
        }
    
        public Gender getGender() {
            return gender;
        }
    
        public int getScore() {
            return score;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    
        @Override
        public boolean equals(Object obj) {
            Student s = (Student) obj;
            return name.equals(s.getName());
        }
    }

    위 소스코드는 예제에서 사용할 학생의 정보를 담는 클래스인 Student 클래스이다.

     

    1. 기본 컬렉션(List, Set 등) 수집하기

    스트림에서 collect() 메서드는 매개 변수에 따라 쓰임새가 달라지는데, 매개 변수를 Collector<T, A, R>로 설정할 경우 주로 특정 요소를 기본 컬렉션에 수집할 때 사용된다. Collector<T, A, R>에서 T는 요소이고, A는 누적기이며, R은 요소가 저장될 컬렉션이다. 즉, T 요소를 A 누적기가 R에 저장한다는 의미이다.

     

    리턴 타입 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에 저장

    위 표는 Collectors 클래스의 정적 메서드이다. 이를 이용해서 Collector의 구현 객체를 얻어낼 수 있다.

     

    리턴 값인 Collector를 보면, A(누적기)가 '?'로 되어 있는데, 이것은 Collector가 R(컬렉션)에 T(요소)를 저장하는 방법을 알고 있어서 A(누적기)가 필요 없기 때문이다.

    또한, Map과 ConcurrentMap의 차이점은 스레드의 안전성이다. Map이 스레드에 안전하지 않고, ConcurrentMap이 스레드에 안전하기 때문에 멀티스레드 환경에서는 ConcurrentMap을 쓰는 것을 권장한다.

     

    public class Main {
        public static void main(String[] args) {
            List<Student> studentList = Arrays.asList(
                    new Student("홍길동", Gender.MALE, 90),
                    new Student("이순신", Gender.MALE, 87),
                    new Student("임꺽정", Gender.MALE, 93)
            );
            
            List<Student> maleList = studentList.stream()
                    .filter(s -> s.getGender().equals(Gender.MALE))
                    .collect(Collectors.toList());
        }
    }

    위 소스코드는 전체 학생 중에서 남학생만 필터링해서 별도의 List를 생성하는 코드이다.

     

    public class Main {
        public static void main(String[] args) {
            List<Student> studentList = Arrays.asList(
                    new Student("홍길동", Gender.MALE, 90),
                    new Student("이순신", Gender.MALE, 87),
                    new Student("임꺽정", Gender.MALE, 93),
                    new Student("이강인", Gender.FEMALE, 85),
                    new Student("손흥민", Gender.FEMALE, 77)
            );
    
            HashSet<Student> femaleSet = studentList.stream()
                    .filter(s -> s.getGender().equals(Gender.FEMALE))
                    .collect(Collectors.toCollection(HashSet::new));
        }
    }

    위 소스코드는 전체 학생 중에서 여학생만 필터링해서 별도의 HashSet을 생성하는 코드이다. HashSet 컬렉션을 얻기 위해서는 toCollection() 메서드를 작성해야 한다. 그리고 이 메서드의 매개변수로는 Supplier<Collection<T>>를 넘겨주면 된다.

    또한, HashSet 내에서 객체들이 같은 객체인지 비교하기 위해서는 hashCode() 메서드와 equals() 메서드를 오버라이딩해주어야 한다.

     

    2. 사용자 정의 컨테이너에 수집하기

     

    인터페이스 리턴 타입 메서드(매개 변수)
    Stream R collect(Supplier<R>, BiConsumer<R, ? super T>, BiConsumer<R, R>)
    IntStream R collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer<R, R>)
    LongStream R collect(Supplier<R>, ObjLongConsumer<R>, BiConsumer<R, R>)
    DoubleStream R collect(Supplier<R>, ObjDouble<R>, BiConsumer<R, R>)

    List, Set, Map과 같은 컬렉션이 아니라 사용자 정의 컨테이너 객체에 요소를 수집하는 방법에 대해 알아보겠다. 위에서는 Collector를 매개변수로 전달하는 collect() 메서드를 사용했지만, 이번에는 총 3개의 매개변수를 필요로 하는 collect() 메서드를 사용해야 한다.

     

    첫 번째로, Supplier<R>는 요소들이 수집될 컨테이너 객체(R)를 생성하는 역할을 한다. 싱글 스레드 스트림에서는 단 한 번 Supplier가 실행되고 하나의 컨테이너 객체를 생성한다. 반면, 멀티 스레드 스트림에서는 여러 번 Supplier가 실행되고 스레드 별로 여러 개의 컨테이너 객체를 생성한다. 그리고 최종적으로 하나의 컨테이너 객체로 결합되는 방식이다.

     

    두 번째로, XXXConsumer는 컨테이너 객체(R)에 요소(T)를 수집하는 역할을 한다. 스트림에서 요소를 컨테이너에 수집할 때마다 XXXConsumer가 실행된다.

     

    마지막으로, BiConsumer는 컨테이너 객체(R)를 결합하는 역할을 하는데, 싱글 스레드 스트림에서는 호출되지 않고, 병렬 처리 스트림에서만 호출되어 스레드 별로 생성된 컨테이너 객체를 결합해서 최종 컨테이너 객체를 완성시킨다.

     

    리턴 타입 R은 요소들이 최종 수집된 컨테이너 객체를 의미한다. 싱글 스레드 스트림에서의 리턴 객체는 첫 번째 매개 변수인 Supplier가 생성한 객체지만, 병렬 처리 스트림에서의 리턴 객체는 최종 결합된 컨테이너 객체가 된다.

    이번 게시글에서는 싱글 스레드 스트림을 활용한 예제를 살펴보겠다.

     

    public class MaleStudent {
        private List<Student> list;
        
        public MaleStudent() {
            list = new ArrayList<>();
            System.out.println("[" + Thread.currentThread().getName() + "] MaleStudent()");
        }
        
        /** 요소를 수집 */
        public void accumulate(Student student) {
            list.add(student);
            System.out.println("[" + Thread.currentThread().getName() + "] accumulate()");
        }
        
        /** 두 MaleStudent를 결합(병렬 처리 시에만 호출) */
        public void combine(MaleStudent other) {
            list.addAll(other.getList());
            System.out.println("[" + Thread.currentThread().getName() + "] combine()");
        }
        
        public List<Student> getList() {
            return list;
        }
    }

    위 소스코드는 학생들 중에서 남학생만 수집하는 MaleStudent 컨테이너를 정의한 코드이다.

     

    public class Main {
        public static void main(String[] args) {
            List<Student> studentList = Arrays.asList(
                    new Student("홍길동", Gender.MALE, 90),
                    new Student("이순신", Gender.MALE, 87),
                    new Student("임꺽정", Gender.MALE, 93),
                    new Student("이강인", Gender.FEMALE, 85),
                    new Student("손흥민", Gender.FEMALE, 77)
            );
    
            MaleStudent maleStudent = studentList.stream()
                    .filter(s -> s.getGender().equals(Gender.MALE))
                    .collect(MaleStudent::new, MaleStudent::accumulate, MaleStudent::combine);
        }
    }

    위 코드에서 살펴볼 수 있듯이, MaleStudent 클래스에서 정의한 메서드들을 collect() 메서드에 전달함으로써 사용자 정의 컨테이너에 요소들을 수집할 수 있다.


    출처

    https://steady-coding.tistory.com/315

     

    728x90

    댓글

Designed by Tistory.