#06장 스트림으로 데이터 수집

Raoul-Gabriel Urma 저, 우정은 역 「Modern Java in Action, 2019」를 읽고 정리하였습니다.

공통 코드

  • 이 메뉴 예제를 가지고 여러 연산을 수행할 것임.

public enum CaloricLevel { DIET, NORMAL, FAT }

public class Dish {

    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public Type getType() {
        return type;
    }

    @Override
    public String toString() {
        return name;
    }

    public enum Type {
        MEAT,
        FISH,
        OTHER
    }

    public static final List<Dish> menu = asList(
        new Dish("pork", false, 800, Dish.Type.MEAT),
        new Dish("beef", false, 700, Dish.Type.MEAT),
        new Dish("chicken", false, 400, Dish.Type.MEAT),
        new Dish("french fries", true, 530, Dish.Type.OTHER),
        new Dish("rice", true, 350, Dish.Type.OTHER),
        new Dish("season fruit", true, 120, Dish.Type.OTHER),
        new Dish("pizza", true, 550, Dish.Type.OTHER),
        new Dish("prawns", false, 400, Dish.Type.FISH),
        new Dish("salmon", false, 450, Dish.Type.FISH)
    );

    public static final Map<String, List<String>> dishTags = new HashMap<>();
    static {
        dishTags.put("pork", asList("greasy", "salty"));
        dishTags.put("beef", asList("salty", "roasted"));
        dishTags.put("chicken", asList("fried", "crisp"));
        dishTags.put("french fries", asList("greasy", "fried"));
        dishTags.put("rice", asList("light", "natural"));
        dishTags.put("season fruit", asList("fresh", "natural"));
        dishTags.put("pizza", asList("tasty", "salty"));
        dishTags.put("prawns", asList("tasty", "roasted"));
        dishTags.put("salmon", asList("delicious", "fresh"));
    }
}

내용

  • Collectors 클래스로 컬렉션을 만들고 사용하기

  • 하나의 값으로 데이터 스트림 reduce하기

  • 특별한 reducing 요약 연산

  • 데이터 그룹화와 분할

  • 자신만의 커스텀 컬렉터 개발

들어가기 전

  • 스트림은 중간 연산과 최종 연산으로 나눠진다.

    • 중간 연산: 한 스트림을 다른 스트림으로

      • 예: filter, map

    • 최종 연산: 요소를 소비해서 결과 도출

      • 예: count, findFirst, forEach, reduce

  • reduce 뿐만 아니라 collect도 리듀싱 연산을 수행 가능

  • collect와 reduce 차이

    • collect 메서드는 도출하려는 결과를 누적하는 컨테이너를 바꾸도록 설계된 메서드

    • reduce 메서드는 두 값을 하나로 도출하는 불변형 연산

    • 언제 collect 사용?

      • 가변 컨테이너 관련 작업이면서 병렬성 확보가 필요하면 collect 사용 (7장 자세히)

Collectors.groupingBy 맛보기

람다가 없었다면, 다음과 같이 트랜잭션들을 통화로 그루핑

람다 사용시

6.1 컬렉터란 무엇인가

  • 함수형 프로그래밍에서는 무엇을 원하는지 직접 명시할 수 있어서 어떤 방법으로 이를 얻을지는 신경 쓸 필요가 없다

    • toList와 groupingBy

  • Collectors 클래스의 팩터리 메서드

    • groupingBy 등

  • Collectors에서 제공하는 메서드의 기능

    • 스트림 요소를 하나의 값으로 리듀스하고 요약

    • 요소 그룹화

    • 요소 분할(프레디케이트를 그룹화 함수로 사용)

6.2 리듀싱과 요약

사례들

6.3. 그룹화

사례들

6.4 분할

  • 분할 : 분할 함수라 불리는 프레디케이트를 분류 함수로 사용하는 특수한 그룹화 기능

  • key 형식은 Boolean으로 그룹화 맵은 최대 참 아니면 거짓의 값을 갖는 두 개의 그룹으로 분류됨

    • 예) 채식 요리와 채식이 아닌 요리

6.4.1 분할의 장점

  • 분할 함수가 반환하는 참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지하고 있음

  • 컬렉터를 두 번째 인수로 전달 가능

  • 내부적으로 partitioningBy는 특수한 맵과 두 개의 필드로 구현되어 있음

6.4.2 숫자를 소수와 비소수로 분할하기

  • 예제 - 정수 n을 인수로 받아서 2에서 n까지의 자연수를 소수와 비소수로 나누는 프로그램

    1. 주어진 수가 소수인지 아닌지 판단하는 프레디케이트를 구현한 뒤(isPrime)

    2. isPrime 메서드를 프레디케이트로 이용하고 partitioningBy 컬렉터로 리듀스 해서 분류

  • p.223~224 표6-1 Collectors 클래스의 정적 팩토리 메서드 참고하기

6.5 Collector 인터페이스

  • Collector 인터페이스는 리듀싱 연산을 어떻게 구현할지 제공하는 메서드 집합으로 구성

  • T : 수집될 스트림 항목의 제네릭 형식

  • A : 누적자, 수집 과정에서 중간 결과를 누적하는 객체의 형식

  • R : 수집 연산 결과 객체의 형식(대부분 컬렉션 형식)

  • Stream의 모든 요소를 List로 수집하는 ToListCollector

    • public class ToListCollector<T> implements Collector<T, List<T>, List<T>

1) supplier - 새로운 결과 컨테이너 만들기

  • 빈 결과로 이루어진 Supplier를 반환. 수집 과정에서 빈 누적자 인스턴스를 만드는 파라미터가 없는 함수.

  • 생성자 참조를 전달하는 방법도 있다

2) accumulator - 결과 컨테이너에 요소 추가하기

  • 리듀싱 연산을 수행하는 함수를 반환

  • 메서드 참조 사용

3) finisher - 최종 변환값을 결과 컨테이너로 적용

  • 스트림 탐색을 끝내고 누적자 객체를 최종 결과로 반환하면서 누적 과정을 끝낼 때 호출할 함수를 반환

  • 누적자 객체가 이미 최종 결과인 상황에는 항등 함수를 반환

4) combiner - 두 결과 컨테이너 병합

  • 스트림의 서로 다른 서브파트를 병렬로 처리할 때 누적자가 결과를 어떻게 처리할지 정의

  • toList의 combiner는 두번째 서브파트에서 수집한 항목 리스트를 첫번째 서브파트 결과 리스트의 뒤에 추가하는 방법으로 구현

5) characteristics

  • 컬렉터의 연산을 정의하는 Characteristics 형식의 불변 집합을 반환

  • 스트림을 병렬로 리듀스할 것인지, 한다면 어떤 최적화를 선택해야 할지 힌트를 제공

    • UNORDERED

    • CONCURRENT

    • IDENTITY_FINISH

6.7 마치며

  • collect는 스트림의 요소를 요약 결과로 누적하는 다양한 방법(컬렉터라 불리는)을 인수로 갖는 최종 연산이다

  • 스트림의 요소를 하나의 값으로 리듀스하고 요약하는 컬렉터뿐 아니라 최솟값, 최댓값, 평균값을 계산하는 컬렉터 등이 미리 정의되어 있다

  • 미리 정의된 컬렉터인 groupingBy로 스트림의 요소를 그룹화하거나, partitioningBy로 스트림의 요소를 분할할 수 있다

  • 컬렉터는 다수준의 그룹화, 분할, 리듀싱 연산에 적합하게 설계되어 있다

  • Collector 인터페이스에 정의된 메서드를 구현해서 커스텀 컬렉터를 개발할 수 있다

Last updated