6. 스트림으로 데이터 수집

weekbelt·2023년 4월 3일
0

6.1 리듀싱과 요약

컬렉터로 스트림의 모든 항목을 하나의 결과로 합칠 수 있다.

long howManyDishes = menu.stream().count();

6.1.1 스트림값에서 최댓값과 최솟값 검색

Comparator<Dish> dishCaloriesComparator = 
	Comparator.comparingInt(Dish::getCalories);

Optional<Dish> mostCalorieDish = menu.stream()
	.collect(maxBy(dishCaloriesComparator));

스트림에 있는 객체의 숫자 필드의 합계나 평균 등을 반환하는 연산에도 리듀싱 기능이 자주 사용된다.

6.1.2 요약 연산

int totalCalories = menu.stream()
	.collect(summingInt(Dish::getCalories));		// 합계
    
double avgCalories = menu.stream()
	.collect(averagingInt(Dish::getCalories));		// 평균
    
IntSummaryStatistics menuStatistics = menu.stream()
	.collect(summarizingInt(Dish::getCalories));	// 요소 수, 합계, 평균, 최댓값, 최솟값

6.1.3 문자열 연결

joining 메서드는 내부적으로 StringBuilder를 이용해서 문자열을 하나로 만든다.

String shotMenu = menu.stream()
	.map(Dish::getName)
    .collect(joining());
    
String shotMenu = menu.stream()		
	.map(Dish::getName)
    .collect(joining(", ));		// ,를 구분자로 출력

6.1.4 범용 리듀싱 요약 연산

이전에 살펴봤던 모든 컬렉터는 reducing 팩토리 메서드로도 정의할 수 있다. 그럼에도 불구하고 특화된 컬렉터를 사용한 이유는 프로그래밍적 편의성 때문이다.

int totalCalories = menu.stream()
	.collect(reducing(0, Dish::getCalories, (i, j) -> i + j));

reducing 인수

  • 첫 번째 인수: 리듀싱 연산의 시작값 혹은 스트림에 인수가 없을 때는 반환값
  • 두 번째 인수: 변환함수
  • 세 번째 인수: 두 항목을 하나의 값으로 더하는 BinaryOperator

한 개의 인수를 가진 reducing 버전을 이용해서 가장 칼로리가 높은 요리를 찾는 방법도 있다.

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

6.2 그룹화

Map<Dish.Type, List<Dish>> dishesByType =
	menu.stream().collect(groupingBy(Dish::getType));
{
	FISH=[prawns, salmon],
    OTHER=[french fries, rice, season fruit, pizza],
    MEAT=[pork, beef, chicken]
}

6.2.1 그룹화된 요소 조작

 Map<Dish.Type, List<Dish>> caloricDishesByType = menu.stream().collect(
        groupingBy(Dish::getType,
            filtering(dish -> dish.getCalories() > 500, toList())));
  }
{
	OTHER=[french fries, pizza],
    MEAT=[pork, beef],
    FISH=[]
}

6.2.2 다수준 그룹화

Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishedByTypeCaloricLevel =  enu.stream().collect(
        groupingBy(Dish::getType,
            groupingBy((Dish dish) -> {
              if (dish.getCalories() <= 400) {
                return CaloricLevel.DIET;
              }
              else if (dish.getCalories() <= 700) {
                return CaloricLevel.NORMAL;
              }
              else {
                return CaloricLevel.FAT;
              }
            })
        )
    );
{
	MEAT={
      DIET=[chicken], 
      NORMAL=[beef], 
      FAT=[pork]
    },
    FISH={
      DIET=[prawns], 
      NORMAL=[salmon]
    },
    OTHER={
      DIET=[rice, seasonal fruit], 
      NORMAL=[french fries, pizza]
    }
}

6.2.3 서브그룹으로 데이터 수집

Map<Dish.Type, Long> typesCount = menu.stream().collect(
	groupingBy(Dish::getType, counting()));
{
	MEAT=3,
    FISH=2,
    OTHER=4
}

요리의 종류를 분류하는 컬렉터로 메뉴에서 가장 높은 칼로리를 가진 요리를 찾는 프로그램을 구현할 수 있다.

Map<Dish.Type, Optional<Dish>> mostCaloricByType = 
	menu.stream()
    	.collect(groupingBy(Dish::getType,
        					maxBy(comparingInt(Dish::getCalories))));
{
	FISH=Optional[salmon],
    OTHER=Optional[pizza],
    MEAT=Optional[pork]
}

컬렉터 결과를 다른 형식에 적용하기

Map<Dish.Type, Dish> mostCaloricByType = 
  menu.stream().collect(
          groupingBy(Dish::getType,		// 분류 함수
              collectingAndThen(
                  maxBy(comparingInt(Dish::getCalories)),	// 감싸인 컬렉터
                  Optional::get)));	// 변환 함수

팩토리 메서드 collectingAndThen은 적용할 컬렉터와 변환 함수를 인수로 받아 다른 컬렉터를 반환한다.

참고자료

profile
백엔드 개발자 입니다

0개의 댓글