LoginSignup
62
54

More than 5 years have passed since last update.

Javaでリストの集約(Collectors.groupingBy)

Last updated at Posted at 2018-04-14

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}
62
54
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
54