Collectors.groupingByを用いて、Listをグルーピングし、Key-ListのMap型データを取得できます。実用的なサンプルコードをまとめました。

対象オブジェクト

public class Product {

    private String productId;

    private String productName;

    private String productType;

    private BigDecimal price;

    private long qty;

    // getter setter

上記のオブジェクトを用いて、Listを以下のイメージで作成する。

1 productName:iPhone X        productType:iPhone    price:120000   qty:2
2 productName:iPhone 8 Plus   productType:iPhone    price:110000   qty:3
3 productName:iPhone 8        productType:iPhone    price:100000   qty:1
4 productName:Galaxy S9       productType:Android   price:100000   qty:4
5 productName:Galaxy S9 plus  productType:Android   price:110000   qty:3
6 productName:Windows phone   productType:Others    price:80000    qty:1
7 productName:Windows phone   productType:Others    price:85000    qty:2

単一項目のグルーピング

Collectors.groupingByで単一項目のグルーピングを行う。

// 単一項目のグルーピング
Map<String, List<Product>> grpByType = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType));

実行結果:

{iPhone=[productId:001 productName:iPhone X productType:iPhone price:120000 qty:2, productId:002 productName:iPhone 8 Plus productType:iPhone price:110000 qty:3, productId:003 productName:iPhone 8 productType:iPhone price:100000 qty:1], 
 Others=[productId:006 productName:Windows phone productType:Others price:80000 qty:1, productId:007 productName:Windows phone productType:Others price:85000 qty:2], 
 Android=[productId:004 productName:Galaxy S9 productType:Android price:100000 qty:4, productId:005 productName:Galaxy S9 plus productType:Android price:110000 qty:3]}

複数項目のグルーピング

方法1

複合キーを事前に作成し、グルーピング時にその複合キーを使用する。

// 複合キーを作成
Function<Product, String> compositeKey = prd -> {
    StringBuffer sb = new StringBuffer();
    sb.append(prd.getProductType()).append("-").append(prd.getProductName());
    return sb.toString();
};

// 複合キーでグルーピング
Map<String, List<Product>> grpByComplexKeys = prdList.stream().collect(
                Collectors.groupingBy(compositeKey));

実行結果:

{iPhone-iPhone 8 Plus=[productId:002 productName:iPhone 8 Plus productType:iPhone price:110000 qty:3], 
 Android-Galaxy S9 plus=[productId:005 productName:Galaxy S9 plus productType:Android price:110000 qty:3], 
 Others-Windows phone=[productId:006 productName:Windows phone productType:Others price:80000 qty:1, productId:007 productName:Windows phone productType:Others price:85000 qty:2], 
 Android-Galaxy S9=[productId:004 productName:Galaxy S9 productType:Android price:100000 qty:4], 
 iPhone-iPhone 8=[productId:003 productName:iPhone 8 productType:iPhone price:100000 qty:1], 
 iPhone-iPhone X=[productId:001 productName:iPhone X productType:iPhone price:120000 qty:2]}

方法2

複合キーのクラスを作成し、比較のため、hashCodeとequalsをオーバーライドする。

public class Keys {

    String productType;
    String productName;

    public Keys(String productType, String productName) {
        this.productType = productType;
        this.productName = productName;
    }

    @Override
    public int hashCode() {
        // 省略(IDEから自動生成)
    }

    @Override
    public boolean equals(Object obj) {
        // 省略(IDEから自動生成)
    }

作成された復号キークラスでグルーピングを行う。

// 複合キーでグルーピング
Map<Keys, List<Product>> grpByComplexKeys = prdList.stream().collect(
                Collectors.groupingBy(prd -> new Keys(prd.getProductType(), prd.getProductName())));

実行結果:

{Android-Galaxy S9 plus=[productId:005 productName:Galaxy S9 plus productType:Android price:110000 qty:3], 
 iPhone-iPhone 8 Plus=[productId:002 productName:iPhone 8 Plus productType:iPhone price:110000 qty:3], 
 Android-Galaxy S9=[productId:004 productName:Galaxy S9 productType:Android price:100000 qty:4], 
 iPhone-iPhone 8=[productId:003 productName:iPhone 8 productType:iPhone price:100000 qty:1], 
 iPhone-iPhone X=[productId:001 productName:iPhone X productType:iPhone price:120000 qty:2], 
 Others-Windows phone=[productId:006 productName:Windows phone productType:Others price:80000 qty:1, productId:007 productName:Windows phone productType:Others price:85000 qty:2]}

複数回でグルーピング

// 2回グルーピング
Map<String, Map<String, List<Product>>> grpByTypeAndGrpByName = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType, 
                    Collectors.groupingBy(Product::getProductName)));

実行結果:

{iPhone={iPhone 8=[productId:003 productName:iPhone 8 productType:iPhone price:100000 qty:1], iPhone X=[productId:001 productName:iPhone X productType:iPhone price:120000 qty:2], iPhone 8 Plus=[productId:002 productName:iPhone 8 Plus productType:iPhone price:110000 qty:3]}, 
 Others={Windows phone=[productId:006 productName:Windows phone productType:Others price:80000 qty:1, productId:007 productName:Windows phone productType:Others price:85000 qty:2]}, 
 Android={Galaxy S9=[productId:004 productName:Galaxy S9 productType:Android price:100000 qty:4], Galaxy S9 plus=[productId:005 productName:Galaxy S9 plus productType:Android price:110000 qty:3]}}

グループの合計

int, longなどの合計

Collectors.summingIntCollectors.summingLongでint, longの合計を求める。

// INTの合計
Map<String, Long> grpByTypeSumQty = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType, 
                    Collectors.summingLong(Product::getQty)));

実行結果:

{iPhone=6, Others=3, Android=7}

BigDecimalの合計

Collectors.reducingでBigDecimalの合計を求める。

// BigDeciamlの合計
Map<Object, BigDecimal> grpByTypeSum = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType, 
                    Collectors.reducing(BigDecimal.ZERO, Product::getPrice, BigDecimal::add)));

実行結果:

{iPhone=330000, Others=165000, Android=210000}

グループの最大値

int, longの最大値

Comparator.comparingIntComparator.comparingLongを用いて、グループの最大値を求める。

Map<String, Optional<Product>> grpByTypeMaxInt = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType, 
                    Collectors.maxBy(Comparator.comparingLong(Product::getQty))));

実行結果:

{iPhone=Optional[productId:002 productName:iPhone 8 Plus productType:iPhone price:110000 qty:3], 
 Others=Optional[productId:007 productName:Windows phone productType:Others price:85000 qty:2], 
 Android=Optional[productId:004 productName:Galaxy S9 productType:Android price:100000 qty:4]}

BigDecimalの最大値

Comparator.comparingでグループの最大値を取れる。

Map<String, Optional<Product>> grpByTypeMaxDecimal2 = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType, 
                    Collectors.maxBy(Comparator.comparing(Product::getPrice))));

自作のComparatorでも可能。

// BigDecimalの最大値
Map<String, Optional<Product>> grpByTypeMaxDecimal = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType, 
                    Collectors.maxBy(new Comparator<Product>() {

            @Override
            public int compare(Product o1, Product o2) {
                return o1.getPrice().compareTo(o2.getPrice());
            }

        }))
);

実行結果:

{iPhone=Optional[productId:001 productName:iPhone X productType:iPhone price:120000 qty:2], 
 Others=Optional[productId:007 productName:Windows phone productType:Others price:85000 qty:2], 
 Android=Optional[productId:005 productName:Galaxy S9 plus productType:Android price:110000 qty:3]}

複雑な計算式

事前に計算式(単価x数量)を定義し、それを用いてグループの合計値を求める。

// 計算式(単価x数量)
Function<Product, BigDecimal> calcFunction = prd -> 
                prd.getPrice().multiply(new BigDecimal(prd.getQty()));

// 集約
Map<String, BigDecimal> grpByTypeToSum = prdList.stream().collect(
                Collectors.groupingBy(Product::getProductType,
                    Collectors.reducing(BigDecimal.ZERO, calcFunction, BigDecimal::add)));

実行結果:

{iPhone=670000, Others=250000, Android=730000}
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.