본문 바로가기
독서/모던 자바 인 액션

[자바] 스트림을 이용한 데이터 수집 - 그룹화

by kriorsen 2023. 10. 10.

9.1 그룹화

1. Collectors.groupingBy

데이터를 하나의 카테고리를 주제로 분류하고 싶을 경우에는 Collectors의 groupingBy 메서드를 사용하면 됩니다.

Map<Category, List<Book>> bookesByCategory = bookshelf.stream().collect(groupingBy(Book::getCategory));

위와 같이 코드를 작성하면 Category 종류별로 관련된 책들의 리스트를 조회할 수 있습니다. 그러나 페이지에 따라 책이 가벼운지 무거운지 알고 싶은 경우에는 조금 더 복잡한 코드를 작성해야 합니다.

Map<Thickness, List<Book>> booksByThickness = bookshelf.stream()
	.collect(groupingBy(book -> {
    	if (book.getPage() < 100) return Thickness.THIN;
        if (book.getPage() < 300) return Thickness.NORMAL;
        return Thickness.THICK;
	}));

2. 그룹화된 요소 조작

Map<Category, List<Book>> booksByCategory = 
	bookshelf.stream().filter(book -> book.getPage() > 100)
    .collect(groupingBy(Book::getCategory));

100 페이지가 넘는 책들에 대해서만 그룹화 작업을 하고 싶어서 위와 같은 코드를 작성할 경우 만약 Economy라는 카테고리가 있고 이 항목에 있는 모든 책들이 다 100 페이지를 넘기지 않는다면 Map에 Economy라는 키값이 존재하지 않는다는 문제가 발생합니다.

Map<Category, List<Book>> booksByCategory = bookshelf.stream()
    .collect(groupingBy(Book::getCategory, 
    	filtering(book -> book.getPage() > 100), toList()));
        
public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

위와 같이 Collector 변수에 프레디케이트를 설정하면 속한 값이 없는 Category도 항목에 추가됩니다.

 

3. 다수준 그룹화

Map<Category, Map<Thickness, List<Book>> booksByCateogryThickness = 
	bookshelf.stream().collect(
    	groupingBy(Book::getCategory,
        	groupingBy(book -> {
            	if (book.getPage() < 100) return Thickness.THIN;
                if (book.getPage() < 300) return Thickness.NORMAL;
                return Thickness.THICK;
			})
		)
	);
    
// {ECONOMY = {THIN=[book1, book2], NORMAL=[book3]}, SCIENCE = {THICK=[book4], THIN=[book5]}, ...}

grouping 내부에 grouping 메서드를 한 번 더 사용함으로써 n수준의 맵을 만들 수 있습니다.

 

4. 서브그룹으로 데이터 수집

public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }

groupingBy(classifier) 메서드가 groupingBy(classifier, toList())의 축약형인만큼 두 번째 매개변수의 인자를 바꿈으로써 그룹화 메서드의 다양한 활용을 할 수 있습니다. 

Map<Category, Long> countsByCategory = bookshelf.stream()
                .collect(Collectors.groupingBy(
                        Book::getCategory,
                        Collectors.counting()
                ));

이 외에도 toSet, summingInt, maxBy 등의 여러 활용을 할 수 있습니다.