Java Tips | コレクション:合計算出

Java Java
スポンサーリンク

「合計算出」は“一覧から一つの数字にギュッと圧縮する”基本テク

業務コードで一番よく出てくる集計が「合計」です。
売上金額の合計、ポイントの合計、在庫数の合計、工数の合計…。

どれも「たくさんの値 → ひとつの数字」にギュッと圧縮する処理です。
だからこそ、ここを毎回 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; }
}
Java

Stream だけで書くとこうなります。

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」に置き換えられないか眺めてみてください。

その小さな整理が、
「一覧から“欲しい数字”を迷いなく取り出せるエンジニア」への、
確かな一歩になります。

タイトルとURLをコピーしました