본문으로 바로가기

[Modern Java in Action] Stream 2편

category 개발 서적 정리 2020. 7. 30. 14:45

그룹화

프로그래밍을 하다보면 하나 이상의 특성으루 분류해서 그룹화하는 연산을 많이 수행해야 한다. 자바8 이전에는 그룹화하는 과정이 복잡하고 까다로웠지만, 자바8의 함수형을 이용하면 한 줄의 코드로 그룹화를 구현할 수 있다.

다음은 음식의 Type별로 그룹화를 진행하는 코드다.

  1. 자바8 이전의 코드

    Map<Dish.Type, List<Dish>> dishesByType = new HashMap<>();  
    for (Dish dishes : Dish.menu) {  
    Dish.Type type = dishes.getType();  
    List<Dish> list = dishesByType.get(type);  
    if (list == null) {  
        list = new ArrayList<>();  
    dishesByType.put(type, list);  
    }  
    list.add(dishes);  
    }
  2. 자바8 함수형 이용

    Map<Dish.Type, List<Dish>> dishesByTypeForStream = Dish.menu.stream().collect(groupingBy(Dish::getType));

스트림의 각 요리에서 Dish.Type과 일치하는 모든 요리를 추출하는 함수를 gropingBy 메서드로 전달했다. 전달한 함수를 기준으로 스트림이 그룹화되므로 이를 '분류 함수' 라고 부른다.

복잡한 분류
위 예제코드처럼 단순한 속성 접근자 대신 더 복잡한 분류 기준이 필요한 상황을 생각해보자.
칼로리를 기준으로 400칼로리 이하는 'DIET', 400~700칼로리는 'NORMAL', 그 이상은 'FAT'으로 분류한다고 가정하자.

먼저 enum을 정의한다.

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

그룹화된 요소 조작

요소를 그룹화 한 다음에는, 각 결과 그룹의 요소를 조작하는 연산이 필요하다.
예를들어, 500칼로리가 넘는 요리만 필터한다고 가정하자. 아래 코드처럼 프레디케이트 필터를 적용해 문제를 해결할 수 있다고 생각할 것이다.

Map<Dish.Type , List<Dish>> caloricDishesType =  
        Dish.menu.stream().filter(t->t.getCalories() >500).collect(groupingBy(Dish::getType));

이 코드의 결과값은 아래와 같다.

{MEAT=[pork, beef], OTHER=[french fries, pizza]}

위 코드로도 문제를 해결할 수 있지만, 단점이 존재한다. 바로 맵 형태로 되어있으므로 프레디케이트(filter)에 만족하지 못하는 음식들은 map에서 key 자체가 사라져버린다.

이 문제를 해결하기 위해 Collectors 클래스는 일반적인 분류함수에 Collector 형식의 두 번째 인수를 갖도록 groupingBy 메서드를 오버로드해 이 문제를 해결한다.

 

스트림 메서드 정리