스트림 연산 Part.2

그룹화

데이터 집합을 하나 이상의 특성으로 분류해서 그룹화하는 연산도 데이터베이스에서 많이 수행되는 작업입니다. 함수형을 이용하면 가독성 있는 한 줄의 코드로 그룹화할 수 있는 일들이 많습니다. 단순한 속성 접근자 대신 더 복잡한 분류 기준이 필요한 상황에서는 메서드 레퍼런스가 아니라 람다 표현식으로 구현할 수 있습니다. 아래 예제에선 Dish.Type과 일치하는 모든 요리를 추출하는 함수를 groupingBy 메서드로 전달하였습니다.


private static Map<Dish.Type, List<Dish>> groupDishesByType() {
  return menu.stream().collect(groupingBy(Dish::getType));
}

enum CaloricLevel {DIET, NORMAL, FAT}

private static Map<CaloricLevel, List<Dish>> groupDishesByCaloricLevel() {
  return menu.stream().collect(
          groupingBy(dish -> {
              if (dish.getCalories() <= 400) {
                  return CaloricLevel.DIET;
              } else if (dish.getCalories() <= 700) {
                  return CaloricLevel.NORMAL;
              } else {
                  return CaloricLevel.FAT;
              }
          })
  );
}

다수준 그룹화

아래 코드는 다수준으로 그룹화 할 수 있습니다. 앞서본 그룹화를 한 번 더 적용하면 몇 수준의 그룹화도 가능합니다.


private static Map<Dish.Type, Map<CaloricLevel, List<Dish>>> groupDishedByTypeAndCaloricLevel() {
    return menu.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;
                        }
                    })
            )
    );
}

서브그룹

분류 함수 한 개의 인수를 갖는 groupingBy(Function<? super T,? extends K> classifier)는 사실 groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)에서 Collector<? super T,A,D> downstreamtoList로 축약한 형태라 할 수 있습니다. 만약 마지막에 반환하는 형태가 Optional이 필요가 없을 경우 collectingAndThen으로 컬렉터가 반환한 결과를 다른 형식으로 활용할 수 있습니다.


private static Map<Dish.Type, Optional<Dish>> mostCaloricDishesByType() {
    return menu.stream().collect(
            groupingBy(Dish::getType,
                    reducing((Dish d1, Dish d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)));
}

private static Map<Dish.Type, Dish> mostCaloricDishesByTypeWithoutOprionals() {
    return menu.stream().collect(
            groupingBy(Dish::getType,
                    collectingAndThen(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2), Optional::get)
            )
    );
}

분할

분할은 분할 함수라 불리는 프레디케이트를 분류 함수로 사용하는 특수화 그룹화 기능입니다. 분할 함수인 partitioningBy는 불린을 반환(public static <T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)하므로 맵의 키 형식은 Boolean입니다. 결과적으로 그룹화 맵은 최대 두 개의 그룹으로 분류됩니다. 분할 함수가 반환하는 참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지한다는 것이 분할의 장점입니다.


private static Map<Boolean, List<Dish>> partitionByVegeterian() {
  return menu.stream().collect(partitioningBy(Dish::isVegetarian)); // 분할함수
}

참고