集計は「一覧から“知りたい数字”だけを取り出す」技
集計は、ざっくり言うと
「たくさんのデータから、意味のある数字を取り出す」ことです。
売上一覧から「合計金額」「平均単価」「件数」を出す。
ユーザー一覧から「都道府県ごとの人数」を出す。
アクセスログから「日別のアクセス数」を出す。
こういう「一覧 → 数字」の変換を、
安全に・読みやすく・再利用しやすく書くための考え方とユーティリティが、
“コレクション集計”です。
一番基本:合計・平均・最大・最小・件数
Stream を使ったシンプルな数値集計
まずは、数値の List に対する基本的な集計から押さえましょう。
import java.util.List;
public class BasicAggregation {
public static void main(String[] args) {
List<Integer> prices = List.of(100, 200, 300);
int sum = prices.stream()
.mapToInt(Integer::intValue)
.sum();
double avg = prices.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
int max = prices.stream()
.mapToInt(Integer::intValue)
.max()
.orElse(0);
int min = prices.stream()
.mapToInt(Integer::intValue)
.min()
.orElse(0);
long count = prices.stream()
.count();
}
}
Javaここでの重要ポイントは二つです。
一つ目は、mapToInt で「数値ストリーム」に変換してから集計していること。sum や average は「数値専用のメソッド」なので、mapToInt / mapToLong / mapToDouble を通すのが基本パターンです。
二つ目は、average() や max() が Optional を返すので、orElse(0) のように「データが空だったときの値」を決めていること。
ここをユーティリティに閉じ込めておくと、呼び出し側が楽になります。
集計ユーティリティとしてまとめる
「毎回 Stream 書きたくない」を解消する
同じような集計を何度も書くなら、
ユーティリティメソッドにしてしまうとスッキリします。
import java.util.List;
public final class Aggregates {
private Aggregates() {}
public static int sumInt(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0;
}
return source.stream()
.mapToInt(Integer::intValue)
.sum();
}
public static double averageInt(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0.0;
}
return source.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
}
}
Java使い方はこうです。
List<Integer> prices = List.of(100, 200, 300);
int sum = Aggregates.sumInt(prices);
double avg = Aggregates.averageInt(prices);
Javaここでの重要ポイントは、
「null や空リストの扱いをユーティリティ側で決めている」ことです。
呼び出し側は「とりあえず合計が欲しい」「平均が欲しい」とだけ考えればよく、null や Optional の細かい扱いから解放されます。
オブジェクト一覧から「特定の項目」を集計する
例:商品一覧から「価格の合計」「平均価格」を出す
業務では、単なる List<Integer> よりも、
「オブジェクトの特定のフィールドを集計したい」ことがほとんどです。
class Item {
private final String name;
private final int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Javaこれを集計するユーティリティは、こう書けます。
import java.util.List;
import java.util.function.ToIntFunction;
public final class Aggregates {
private Aggregates() {}
public static <T> int sumInt(List<T> source, ToIntFunction<? super T> mapper) {
if (source == null || source.isEmpty()) {
return 0;
}
return source.stream()
.mapToInt(mapper)
.sum();
}
public static <T> double averageInt(List<T> source, ToIntFunction<? super T> mapper) {
if (source == null || source.isEmpty()) {
return 0.0;
}
return source.stream()
.mapToInt(mapper)
.average()
.orElse(0.0);
}
}
Java使い方はこうです。
List<Item> items = List.of(
new Item("A", 100),
new Item("B", 200),
new Item("C", 300)
);
int totalPrice = Aggregates.sumInt(items, Item::getPrice);
double avgPrice = Aggregates.averageInt(items, Item::getPrice);
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「ToIntFunction<? super T> mapper が“どの項目を集計するか”を表している」こと。Item::getPrice を渡すことで、「価格を集計する」という意図が明確になります。
二つ目は、「ユーティリティ側は“どう集計するか”だけを知っていて、“何を集計するか”は呼び出し側が決める」構造になっていること。
これにより、同じメソッドで「価格」「数量」「ポイント」など、何でも集計できます。
三つ目は、「null や空リストの扱いを一箇所に閉じ込めている」こと。
プロジェクト全体で「空なら 0」「空なら 0.0」といったルールを統一できます。
グルーピングと組み合わせた「グループごとの集計」
例:カテゴリごとの合計金額
集計が本領を発揮するのは、「グルーピング」と組み合わせたときです。
例えば、商品を「カテゴリごとの合計金額」にしたい場合。
class Item {
private final String category;
private final int price;
public Item(String category, int price) {
this.category = category;
this.price = price;
}
public String getCategory() { return category; }
public int getPrice() { return price; }
}
JavaStream だけで書くとこうなります。
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
List<Item> items = List.of(
new Item("食品", 100),
new Item("食品", 200),
new Item("雑貨", 300)
);
Map<String, Integer> sumByCategory =
items.stream()
.collect(Collectors.groupingBy(
Item::getCategory,
Collectors.summingInt(Item::getPrice)
));
Javaここでの重要ポイントは、
「groupingBy(カテゴリ, summingInt(価格)) という一行が、
“カテゴリごとに価格を合計する”という業務仕様をそのまま表している」ことです。
グルーピングと集計を一気に書けるので、
「まず Map に詰めてから for で回して…」という手作業より、
はるかに読みやすく・バグも入りにくくなります。
集計結果を「読みやすい形」にラップする
合計・平均・最大・最小をまとめて返す
「合計だけ」「平均だけ」ではなく、
「合計・平均・最大・最小・件数をまとめて欲しい」ことも多いです。
その場合は、結果をラップする小さなクラスを用意すると、
呼び出し側がとても扱いやすくなります。
public final class IntSummary {
private final long count;
private final int sum;
private final int min;
private final int max;
private final double average;
public IntSummary(long count, int sum, int min, int max, double average) {
this.count = count;
this.sum = sum;
this.min = min;
this.max = max;
this.average = average;
}
public long getCount() { return count; }
public int getSum() { return sum; }
public int getMin() { return min; }
public int getMax() { return max; }
public double getAverage() { return average; }
}
Javaユーティリティ側はこうです。
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.function.ToIntFunction;
public final class Aggregates {
private Aggregates() {}
public static <T> IntSummary summarizeInt(List<T> source, ToIntFunction<? super T> mapper) {
if (source == null || source.isEmpty()) {
return new IntSummary(0, 0, 0, 0, 0.0);
}
IntSummaryStatistics stats =
source.stream()
.mapToInt(mapper)
.summaryStatistics();
return new IntSummary(
stats.getCount(),
(int) stats.getSum(),
stats.getMin(),
stats.getMax(),
stats.getAverage()
);
}
}
Java使い方はこうです。
IntSummary summary = Aggregates.summarizeInt(items, Item::getPrice);
System.out.println(summary.getSum());
System.out.println(summary.getAverage());
System.out.println(summary.getMax());
Javaここでの重要ポイントは、
「集計結果を“意味のあるまとまり”として返している」ことです。
バラバラの数値ではなく、
「これは価格の集計結果だ」と分かる形で扱えるので、
コードの意図がぐっと読みやすくなります。
null を含むデータを集計するときの考え方
「null は 0 とみなすか」「そもそも除外するか」を決める
現実のデータには null が混ざります。
集計するときに重要なのは、
「null をどう扱うか」をプロジェクトとして決めることです。
例えば、「価格が null の商品は集計から除外する」なら、こう書きます。
public static <T> int sumIntIgnoreNull(List<T> source, ToIntFunction<? super T> mapper, java.util.function.Predicate<? super T> nonNullPredicate) {
if (source == null || source.isEmpty()) {
return 0;
}
return source.stream()
.filter(nonNullPredicate)
.mapToInt(mapper)
.sum();
}
Javaあるいは、「null は 0 とみなす」なら、mapper の中で 0 に変換してしまう、という設計もあります。
ここでのポイントは、
「null の扱いも“集計ルールの一部”」だと意識することです。
ユーティリティに閉じ込めておけば、
呼び出し側は「この集計は null をどう扱うのか」を意識せずに済みます。
まとめ:集計ユーティリティで身につけてほしい感覚
コレクションの集計は、
単に「sum を呼ぶテクニック」ではなく、
「一覧から“知りたい数字”を、安全に・一貫して取り出すための設計」です。
数値の List に対する基本的な sum / average / max / min / count を押さえる。
オブジェクト一覧に対しては、「どの項目を集計するか」を関数(Item::getPrice など)で渡す。
グルーピングと組み合わせて、「カテゴリごとの合計」「ステータスごとの件数」などを一行で書けるようにする。
よく使う集計はユーティリティにまとめ、null や空データの扱いを一箇所に閉じ込める。
必要なら「集計結果クラス」を用意して、合計・平均・最大・最小・件数をひとまとまりで扱う。
もしあなたのコードのどこかに、
同じような for 文で合計や件数を手作業で計算している部分があれば、
それを一度「集計ユーティリティ+Stream ベース」に置き換えられないか眺めてみてください。
その小さな整理が、
「一覧から“意味のある数字”を、迷いなく取り出せるエンジニア」への、
確かな一歩になります。
