[Java]StreamのgroupingByで、カテゴリごとの合計(Sum)を求める方法
前提
例えば以下のような、カテゴリとその量が定義されているクラスがあったとします。
public record Obj( String category, int amount ) { }
そのリストから、各カテゴリごとの、量の集計処理を考えます。
// AとBはそれぞれいくつ? List<Obj> objs = List.of( new Obj("A", 100), new Obj("A", 200), new Obj("B", 300), new Obj("B", 400), new Obj("B", 500) );
結果がMap
int, long, doubleの場合
Collectors#groupingBy()
と、Collectors#summingInt()
を使用します。
objs.stream().collect(Collectors.groupingBy( Obj::category, // キーのマッピング Collectors.summingInt(Obj::amount) // オブジェクトから合計対象値のマッピング )); > {A=300, B=1200}
summingDouble
やsummingLong
も用意されているので、型にあわせて使用することができます。
それ以外の型の場合
それ以外の型の場合はCollectors#reducing
にて処理ができます。
objs.stream().collect(Collectors.groupingBy( Obj::category, Collectors.reducing( 0, // 初期値 今回はintの初期値なので0 Obj::amount, // ↑でも出てきた、値のマッピング Integer::sum // それぞれの値に対して行う処理 今回はintの合計のためsum ) )); > {A=300, B=1200}
結果が元の型のCollection
上記の結果からのオブジェクトの生成も可能ですが、以下のような書き方はどうでしょうか。
この書き方であれば、集計対象が2つ以上になった場合も対応可能です。
objs.stream().collect(Collectors.groupingBy( Obj::category, Collectors.reducing((o1, o2) -> new Obj(o1.category(), o1.amount() + o2.amount())) )).values().stream().map(Optional::get).collect(Collectors.toSet()); > [Obj[category=A, amount=300], Obj[category=B, amount=1200]]
参考
Collectors (Java SE 17 & JDK 17)
Group by and sum objects like in SQL with Java lambdas? - Stack Overflow