Java

[Java] 스트림으로 데이터 수집 - 요약 연산

snail voyager 2023. 8. 5. 20:07
728x90
반응형

컬렉터

  • Stream.collect 메서드의 인수로 스트림의 항목을 컬렉션으로 재구성
  • 컬렉터로 스트림의 모든 항목을 하나의 결과로 합침
  • Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 지정
  • collect로 결과를 수집하는 과정을 간단하면서도 유연한 방식으로 정의할 수 있음
  • 스트림에 collect를 호출하면 스트림의 요소에 리듀싱 연산이 수행
  • 리듀싱 연산을 이용해서 스트림의 각 요소를 방문하면서 컬렉터가 작업을 처리
  • Collectors 유틸리티 클래스는 자주 사용하는 Collector 인스턴스를 생성할 수 있는 정적 팩토리 메서드 제공

Collectos에서 미리 정의된 컬렉터

  • 스트림 요소를 하나의 값으로 리듀스하고 요약
  • 요소 그룹화
  • 요소 분할

최대값과 최소값 검색

  • Collectors.maxBy, Collectors.minBy 인수로 Comparator 사용
  • Optional 반환하여 값이 존재하지 않을 수 있음
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator));

summingInt

  • 스트림(Stream)의 요소들을 정수 필드를 기준으로 합산하여 총합을 구하는 Collectors 클래스의 메서드
  •  ToIntFunction<T> 타입을 인자로 받으며, 이는 요소들을 정수 값으로 변환하는 람다식
  • summingLong, summingDouble
  • averagingInt, averagingLong, averagingDouble
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

summarizingInt

  • 스트림의 요소들에 대해 최소값, 최대값, 합계, 평균 등의 요약 정보를 한 번에 얻을 수 있다
  • IntSummaryStatistics 객체를 반환하며, IntSummaryStatistics 객체에는 스트림의 정수 요소들에 대한 다양한 통계 정보가 저장
  • 통계 정보를 간편하게 얻을 수 있도록 도와주며, 스트림 요소들의 합계, 평균 등을 빠르게 계산
  • summarizingLong, summarizingDouble, LongSummaryStatics, DoubleSummaryStatics
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

IntSummaryStatistics statistics = numbers.stream()
        .collect(Collectors.summarizingInt(num -> num));

System.out.println("Count: " + statistics.getCount());
System.out.println("Sum: " + statistics.getSum());
System.out.println("Min: " + statistics.getMin());
System.out.println("Max: " + statistics.getMax());
System.out.println("Average: " + statistics.getAverage());

joining 문자열 연결

  • 스트림의 각 객체에 toString 메서드를 호출해서 추출한 모든 문자열을 하나의 문자열로 연결해서 반환
  • 내부적으로 StringBuilder를 이용해서 문자열을 합침
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));	//구분자 추가

toMap() Map 수집

  • Collectors.toMap() 메서드는 스트림 요소를 Map으로 수집하는 데 사용
  • 세 번째 인수인 병합 함수 사용, 키 충돌이 발생할 때 두 값을 합치는 방법
public class MergeCollisionExample {
    public static void main(String[] args) {
        List<Person> people = List.of(
            new Person(1, "Alice"),
            new Person(2, "Bob"),
            new Person(3, "Charlie"),
            new Person(2, "David") // 키 충돌을 일으킬 요소
        );

        // 스트림을 사용하여 ID를 키로, 이름을 값으로 하는 Map을 생성하며 키 충돌 발생 시 값을 병합
        Map<Integer, String> idToNameMap = people.stream()
            .collect(Collectors.toMap(
                Person::getId,
                Person::getName,
                (existing, replacement) -> existing + ", " + replacement // 병합 시 새로운 값을 기존 값 뒤에 추가
            ));

        // 결과 출력
        idToNameMap.forEach((id, name) -> System.out.println("ID: " + id + ", Name: " + name));
    }
}

reducing 범용 리듀싱 요약 연산

  • 첫번째 인수는 초기값, 두번째 인수는 변환 함수, 세번째 인수는 BinaryOperator 결합 함수
  • 한 개의 인수를 갖는 reducing 메소드는 Optional 객체를 반환
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i,j)-> i+j));

Optional<Dish> mostCalorieDish = menu.stream().collect(reducing(
                                    (d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

Stream 인터페이스의 collect() vs. reduce()

  • collect()는 도출하려는 결과를 누적하는 컨테이너를 바꾸도록 설계된 메서드
  • reduce()는 두 값을 하나로 도출하는 불변형 연산
  • 가변 컨테이너 관련 작업이면서 병렬성을 확보하려면 collect()로 리듀싱 연산을 구현하는 것이 바람직

상황에 맞는 최적의 해법 선택

  • 컬렉터를 이용하는 코드가 더 복잡
  • 대신 재사용성과 커스터마이즈 가능성을 제공하는 높은 수준의 추상화와 일반화를 얻을 수 있음
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum);
int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();
int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum(); //간결하고 가독성이 좋음

 

728x90
반응형