「合計算出」は“一覧から一つの数字にギュッと圧縮する”基本テク
業務コードで一番よく出てくる集計が「合計」です。
売上金額の合計、ポイントの合計、在庫数の合計、工数の合計…。
どれも「たくさんの値 → ひとつの数字」にギュッと圧縮する処理です。
だからこそ、ここを毎回 for 文で手書きするのではなく、
“安全で読みやすい合計ユーティリティ”としてパターン化しておくと、
コード全体がかなりスッキリします。
基本形:数値 List の合計を出す
Stream を使った最小限の書き方
まずは、List<Integer> の合計を出す一番シンプルな形から。
import java.util.List;
public class SumBasic {
public static void main(String[] args) {
List<Integer> prices = List.of(100, 200, 300);
int sum = prices.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println(sum); // 600
}
}
Javaここで押さえてほしい重要ポイントは二つです。
一つ目は、mapToInt(Integer::intValue) で「int 専用のストリーム」に変換していることです。sum() は「数値専用」のメソッドなので、mapToInt(または mapToLong / mapToDouble)を通すのが基本パターンになります。
二つ目は、sum() 自体は空ストリームでも 0 を返してくれることです。
つまり、「データが 0 件でも 0 になる」という挙動が保証されています。
この性質は、合計算出ではとても扱いやすいです。
合計算出をユーティリティメソッドにまとめる
「null や空 List をどう扱うか」を一箇所に閉じ込める
同じような合計処理を何度も書くなら、
ユーティリティにしてしまうとスッキリします。
import java.util.List;
public final class Sums {
private Sums() {}
public static int sumInt(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0;
}
return source.stream()
.mapToInt(Integer::intValue)
.sum();
}
}
Java使い方はこうなります。
List<Integer> prices = List.of(100, 200, 300);
int total = Sums.sumInt(prices);
System.out.println(total); // 600
Javaここでの重要ポイントは、「null と空 List の扱いをユーティリティ側で決めている」ことです。
呼び出し側は「合計が欲しい」とだけ考えればよく、null チェックを毎回書かなくて済みます。
オブジェクト一覧から「特定の項目の合計」を出す
例:商品一覧から「価格の合計」を出す
業務では、単なる 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 Sums {
private Sums() {}
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();
}
}
Java使い方はこうです。
List<Item> items = List.of(
new Item("A", 100),
new Item("B", 200),
new Item("C", 300)
);
int totalPrice = Sums.sumInt(items, Item::getPrice);
System.out.println(totalPrice); // 600
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「ToIntFunction<? super T> mapper が“どの項目を合計するか”を表している」ことです。Item::getPrice を渡すことで、「価格を合計する」という意図が明確になります。
二つ目は、「ユーティリティ側は“どう合計するか”だけを知っていて、“何を合計するか”は呼び出し側が決める」構造になっていることです。
これにより、同じメソッドで「価格」「数量」「ポイント」など、何でも合計できます。
三つ目は、「null や空 List の扱いを一箇所に閉じ込めている」ことです。
プロジェクト全体で「空なら 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)
));
System.out.println(sumByCategory); // {食品=300, 雑貨=300}
Javaここでの重要ポイントは、
「groupingBy(カテゴリ, summingInt(価格)) という一行が、
“カテゴリごとに価格を合計する”という業務仕様をそのまま表している」ことです。
「まず Map に詰めてから for で回して…」という手作業より、
はるかに読みやすく、バグも入りにくくなります。
合計結果を「意味のある型」で表現する
ただの int ではなく「集計結果」としてラップする
合計だけでなく、「件数」や「平均」も一緒に扱いたい場面では、
結果をラップする小さなクラスを用意すると、コードがぐっと読みやすくなります。
public final class IntTotal {
private final long count;
private final int sum;
public IntTotal(long count, int sum) {
this.count = count;
this.sum = sum;
}
public long getCount() { return count; }
public int getSum() { return sum; }
}
Javaユーティリティ側はこうです。
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.function.ToIntFunction;
public final class Sums {
private Sums() {}
public static <T> IntTotal totalOf(
List<T> source,
ToIntFunction<? super T> mapper
) {
if (source == null || source.isEmpty()) {
return new IntTotal(0, 0);
}
IntSummaryStatistics stats =
source.stream()
.mapToInt(mapper)
.summaryStatistics();
return new IntTotal(stats.getCount(), (int) stats.getSum());
}
}
Java使い方はこうです。
IntTotal total = Sums.totalOf(items, Item::getPrice);
System.out.println(total.getCount()); // 件数
System.out.println(total.getSum()); // 合計金額
Javaここでの重要ポイントは、
「合計を“意味のあるまとまり”として返している」ことです。
単なる int ではなく、「これは価格の合計と件数だ」と分かる形で扱えるので、
コードの意図がぐっと読みやすくなります。
null を含むデータの合計算出
「null を 0 とみなすか」「そもそも除外するか」を決める
現実のデータには null が混ざります。
合計算出で大事なのは、「null をどう扱うか」を決めることです。
例えば、「null は 0 とみなして合計する」なら、こう書けます。
public static int sumIntTreatNullAsZero(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0;
}
return source.stream()
.mapToInt(v -> v == null ? 0 : v)
.sum();
}
Java逆に、「null は集計対象から除外する」なら、こうです。
public static int sumIntIgnoreNull(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0;
}
return source.stream()
.filter(v -> v != null)
.mapToInt(Integer::intValue)
.sum();
}
Javaここでの重要ポイントは、
「null の扱いも“合計算出のルール”の一部」だと意識することです。
ユーティリティに閉じ込めておけば、
呼び出し側は「このメソッドは null をどう扱うのか」を意識せずに済みます。
まとめ:合計算出ユーティリティで身につけてほしい感覚
合計算出は、
単に「足し算をするテクニック」ではなく、
「一覧から“意味のある一つの数字”を、安全に・一貫して取り出すための設計」です。
数値 List なら mapToInt(...).sum() の形を基本として覚える。
オブジェクト一覧では、「どの項目を合計するか」を関数(Item::getPrice など)で渡す汎用ユーティリティにする。
グルーピングと組み合わせて、「カテゴリごとの合計」「ステータスごとの合計」を一行で書けるようにする。
null や空コレクションの扱い(0にする、除外する)をユーティリティ側で統一する。
必要なら「合計+件数」などをラップした結果クラスを用意し、意図が伝わる形で扱う。
もしあなたのコードのどこかに、
毎回 for 文で sum += ... を手作業で書いている部分があれば、
それを一度「合計ユーティリティ+Stream」に置き換えられないか眺めてみてください。
その小さな整理が、
「一覧から“欲しい数字”を迷いなく取り出せるエンジニア」への、
確かな一歩になります。
