java stream partitioningBy(), groupingBy() 분할과 그룹화
Java Stream 분할과 그룹화 (partitioningBy, groupingBy)
최근 reducing() 메서드를 살펴보던 중 API Note 부분에서 'reducing()은 groupingBy 또는 partitioningBy의 downstream에 사용할 때 유용하다.'는 주석을 보게 되었는데요.
평소 stream을 잘 사용하지 않는 편이라 partitioningBy, groupingBy 기능을 처음 접하게 되었고, 자세한 내용이 궁금하여 검색해 본 결과 알아두면 잘 활용할 수 있는 기능이라고 생각되어 정리한 내용입니다.
(그리고 딱 마침 바로 실무에서 활용을 하게 되었는데 활용도도 높고, 상당히 편리한 기능인 것 같습니다.)
partitioningBy(), groupingBy() 개념
분할 메서드 partitioningBy()
//Collector partitioningBy(Predicate predicate)
partitioningBy(Predicate<? super T>)
//Collector partitioningBy(Predicate predicate, Colelctor downstream)
partitioningBy(Predicate<? super T>, Collector<? super T, A, D>)
분할은 스트림의 요소를 지정한 조건에 일치하는 그룹과 일치하지 않는 그룹, 두 가지로 분할하는 것입니다.
그룹화 메서드 groupingBy()
//Collector groupingBy(Function classifier)
groupingBy(Function<? super T, ? extends K>)
//Collector groupingBy(Function classifier, Collector downstream)
groupingBy(Function<? super T, ? extends K>, Collector<? super T, A, D>)
//Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
groupingBy(Function<? super T, ? extends K>, Supplier<M>, Collector<? super T, A, D>)
그룹화는 스트림의 요소를 특정한 기준으로 그룹화하는 것입니다.
***
분할과 그룹화의 결과는 'Map'에 담겨 반환되는데요.
분할인 partitioningBy()의 경우 Predicate를 매개변수로 받고, 그룹화인 groupingBy()의 경우 Function을 매개변수로 받습니다.
stream을 두 개의 그룹으로 나눠야 하는 경우에는 groupingBy() 보다 partitioningBy()가 더 빠릅니다.
그럼 아래 예시 코드를 통해 partitioningBy() 메서드와 groupingBy() 메서드의 사용 방법을 살펴보겠습니다.
코드 예시
***
collect() : 스트림의 요소를 수집하는 최종 연산이며, 매개변수로 컬렉터를 필요로 합니다.
Collector : 스트림의 요소를 수집하기 위한 메서드를 정의해 놓은 인터페이스입니다.
Collectors : Collector를 구현한 클래스입니다. static 메서드로 미리 작성된 컬렉터를 제공합니다.
@Getter
@AllArgsConstructor
public class Stock {
private String name; //종목명
private boolean isKospi; //코스피 여부
private Long marketCap; //시가총액 (단위 억 원)
private Long price; //주가 (단위 원)
private INDUSTRY industry; //업종
enum INDUSTRY {CHEMISTRY, AUTOMOTIVE, ELECTRONIC}
}
(예시에 사용할 Stock Class)
List<Stock> stocks = Arrays.asList(
new Stock("자바신소재", true, 5400L, 1200L, Stock.INDUSTRY.CHEMISTRY),
new Stock("코틀린화학", true, 4100L, 2700L, Stock.INDUSTRY.CHEMISTRY),
new Stock("디비케미칼", false, 11900L, 3100L, Stock.INDUSTRY.CHEMISTRY),
new Stock("디버깅모터스", false, 2200L, 1900L, Stock.INDUSTRY.AUTOMOTIVE),
new Stock("깃모빌리티", true, 3000L, 4500L, Stock.INDUSTRY.AUTOMOTIVE),
new Stock("터미널전자", true, 3200L, 1500L, Stock.INDUSTRY.ELECTRONIC),
new Stock("익셉션전기", false, 4300L, 2000L, Stock.INDUSTRY.ELECTRONIC),
new Stock("캐시반도체", true, 1200L, 3600L, Stock.INDUSTRY.ELECTRONIC)
);
(예시에 사용할 Stock List)
partitioningBy() 사용 예시
//partitioningBy()
Map<Boolean, List<Stock>> stockByIsKospi = stocks.stream()
.collect(Collectors.partitioningBy(Stock::isKospi));
List<Stock> kospiStock = stockByIsKospi.get(true);
List<Stock> kosdaqStock = stockByIsKospi.get(false);
기본적인 분할 예시입니다.
Map<Boolean, List<Stock>> 형태로 반환되며, get(boolean)으로 분할된 List를 얻을 수 있습니다.
//partitioningBy() + counting()
Map<Boolean, Long> stockCountByIsKospi = stocks.stream()
.collect(Collectors.partitioningBy(Stock::isKospi, Collectors.counting()));
Long kospiStockCount = stockCountByIsKospi.get(true);
Long kosdaqStockCount = stockCountByIsKospi.get(false);
기본적인 분할과 함께 Collectors의 counting() 메서드를 사용하여 해당 분할 결과의 count를 구하는 예시입니다.
(counting 메서드 자리에 averagingInt, averagingLong 등의 평균을 구하는 메서드도 사용할 수 있습니다.)
//partitioningBy() + maxBy()
Map<Boolean, Optional<Stock>> topMarketCapByIsKospi = stocks.stream()
.collect(Collectors.partitioningBy(Stock::isKospi, Collectors.maxBy(Comparator.comparingLong(Stock::getMarketCap))));
Optional<Stock> topMarketCapKospiOptionalStock = topMarketCapByIsKospi.get(true);
Optional<Stock> topMarketCapKosdaqOptionalStock = topMarketCapByIsKospi.get(false);
분할과 함께 Collectors의 maxBy() 메서드를 사용하여 marketCap 값이 가장 큰 Stock을 구하는 예시입니다. 이때 반환되는 형식은 Map<Boolean, Optional<Stock>>이 됩니다.
//partitioningBy() + maxBy() + collectingAndThen()
Map<Boolean, Stock> topMarketCapByIsKospiReturnStock = stocks.stream()
.collect(Collectors.partitioningBy(Stock::isKospi,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingLong(Stock::getMarketCap)), Optional::get)));
Stock topMarketCapKospiStock = topMarketCapByIsKospiReturnStock.get(true);
Stock topMarketCapKosdaqStock = topMarketCapByIsKospiReturnStock.get(false);
위 예시에서 반환되는 값이 Optional<Stock>이 아니라 Stock으로 바로 받고 싶은 경우, 다음과 같이 Collectors의 collectingAndThen() 메서드와 Optional::get을 통해 원하는 결과를 받아올 수 있습니다.
//partitioningBy() + partitioningBy()
Map<Boolean, Map<Boolean, List<Stock>>> stockByIsKospiAndPrice = stocks.stream()
.collect(Collectors.partitioningBy(Stock::isKospi, Collectors.partitioningBy(stock -> stock.getPrice() >= 2500)));
List<Stock> kospiStockPrice2500more = stockByIsKospiAndPrice.get(true).get(true);
List<Stock> kosdaqStockPrice2500more = stockByIsKospiAndPrice.get(false).get(true);
분할에 분할을 하는 예시입니다.
groupingBy() 사용 예시
//groupingBy() + toList();
Map<Stock.INDUSTRY, List<Stock>> stockByGroupList = stocks.stream()
.collect(Collectors.groupingBy(Stock::getIndustry, Collectors.toList()));
List<Stock> chemistryList = stockByGroupList.get(Stock.INDUSTRY.CHEMISTRY);
List<Stock> automotiveList = stockByGroupList.get(Stock.INDUSTRY.AUTOMOTIVE);
List<Stock> electronicList = stockByGroupList.get(Stock.INDUSTRY.ELECTRONIC);
//groupingBy() + toSet();
Map<Stock.INDUSTRY, Set<Stock>> stockByGroupSet = stocks.stream()
.collect(Collectors.groupingBy(Stock::getIndustry, Collectors.toSet()));
기본적인 그룹화 예시입니다.
Map<Stock.INDUSTRY, List<Stock>> 형태의 결과가 반환되며, 그룹화 기준이 된 Stock.INDUSTRY가 key가 됩니다.
(List로 가져올 때와 Set으로 가져올 때의 예시입니다.)
//groupingBy() + counting()
Map<Stock.INDUSTRY, Long> stockCountByGrouping = stocks.stream()
.collect(Collectors.groupingBy(Stock::getIndustry, Collectors.counting()));
//groupingBy() + maxBy()
Map<Stock.INDUSTRY, Optional<Stock>> topMarketCapOptionalStockByGrouping = stocks.stream()
.collect(Collectors.groupingBy(Stock::getIndustry, Collectors.maxBy(Comparator.comparingLong(Stock::getMarketCap))));
//groupingBy() + maxBy() + collectingAndThen()
Map<Stock.INDUSTRY, Stock> topMarketCapStockByGrouping = stocks.stream()
.collect(Collectors.groupingBy(Stock::getIndustry,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingLong(Stock::getMarketCap)), Optional::get)));
//groupingBy() + partitioningBy()
Map<Stock.INDUSTRY, Map<Boolean, List<Stock>>> stockByGroupingAndPartitioning = stocks.stream()
.collect(Collectors.groupingBy(Stock::getIndustry, Collectors.partitioningBy(Stock::isKospi)));
그룹화 + counting(), maxBy(), collectingAndThen(), partitioning()에 대한 예시입니다.
(큰 내용이 없기 때문에 결과에 대한 자세한 설명은 따로 필요하지 않을 것 같아 생략하였습니다.)