Java | Java 詳細・モダン文法:Stream API 深掘り – partitioningBy

Java Java
スポンサーリンク

partitioningBy を一言でいうと

Collectors.partitioningBy は、Stream の要素を
「ある条件を満たすかどうか(true / false)」で きっちり 2 つのグループに分ける Collector です。引用元: Java 逆引き集 | partitioningBy(true/false に分割) — 二分集計

groupingBy が「キーごとにいくつでもグループを作る」のに対して、
partitioningBy は「必ず true グループと false グループの 2 つだけ」を作る、というのが一番大きな特徴です。引用元: Collector応用 – partitioningBy, collectingAndThen, reducing の設計判断とレビュー視点


基本形:Predicate で true / false に二分割する

シグネチャと戻り値の型

一番基本の形はこうです。

Map<Boolean, List<T>> result =
        stream.collect(Collectors.partitioningBy(predicate));
Java

ここで predicateT -> boolean な関数、つまり Predicate<T> です。
戻り値は Map<Boolean, List<T>> で、キー true 側に「条件を満たす要素」、false 側に「条件を満たさない要素」が入ります。引用元: Java 逆引き集 | partitioningBy(true/false に分割) — 二分集計

例:偶数と奇数に分ける

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PartitionEvenOdd {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);

        Map<Boolean, List<Integer>> partitioned =
                nums.stream()
                    .collect(Collectors.partitioningBy(n -> n % 2 == 0));

        System.out.println("偶数: " + partitioned.get(true));   // [2, 4, 6]
        System.out.println("奇数: " + partitioned.get(false));  // [1, 3, 5]
    }
}
Java

n -> n % 2 == 0 という条件に対して、
true 側に偶数、false 側に奇数がきれいに分かれます。引用元: Java Stream APIのpartitioningByでデータを条件別に効率的に分割する方法

「条件を満たすもの」と「満たさないもの」を同時に欲しいとき、filter を 2 回書くよりも、partitioningBy で一発で二分する方が意図がはっきりします。


groupingBy との違いを感覚でつかむ

groupingBy でも似たことはできるが…

groupingBy でも、キーを Boolean にすれば似たようなことはできます。

Map<Boolean, List<Integer>> grouped =
        nums.stream()
            .collect(Collectors.groupingBy(n -> n % 2 == 0));
Java

見た目の型は同じですが、設計上の意味が違います。

groupingBy は「キーの種類はいくつあってもよい」一般的なグルーピングです。
partitioningBy は「true / false の 2 グループに“分割する”」ことを前提にした、二分専用の Collector です。引用元: Collectors#partitioningBy (), groupingBy () #Java – Qiita

そのため、コードを読む側にとっては

「これは二分割しているんだな」

と一目で分かる、というのが大きなメリットです。


下流 Collector 付き partitioningBy で「二分集計」する

形:partitioningBy(predicate, downstream)

partitioningBy には、もう一つよく使うオーバーロードがあります。

Map<Boolean, R> result =
        stream.collect(Collectors.partitioningBy(predicate, downstreamCollector));
Java

ここで downstreamCollector は、「true 側」「false 側」それぞれのグループに対してさらに集計を行う Collector です。引用元: Java 逆引き集 | partitioningBy(true/false に分割) — 二分集計

例:合格 / 不合格の人数を数える

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Student {
    private final String name;
    private final int score;
    Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    int getScore() { return score; }
}

public class PartitionPassFailCount {
    public static void main(String[] args) {
        List<Student> students = List.of(
                new Student("Alice", 80),
                new Student("Bob",   40),
                new Student("Carol", 70),
                new Student("Dave",  30)
        );

        Map<Boolean, Long> counts =
                students.stream()
                        .collect(Collectors.partitioningBy(
                                s -> s.getScore() >= 60,      // 合格判定
                                Collectors.counting()         // 各グループの件数
                        ));

        System.out.println("合格者数: " + counts.get(true));   // 2
        System.out.println("不合格者数: " + counts.get(false)); // 2
    }
}
Java

ここでは、

「合格かどうか(true / false)」で二分割し、
それぞれのグループで counting() している

という構造になっています。

partitioningBy(条件, counting()) は、「二分+件数集計」の定番パターンとして覚えておくとかなり使えます。引用元: Java 逆引き集 | partitioningBy(true/false に分割) — 二分集計

例:条件ごとに合計値を出す

同じノリで、「高額 / 低額ごとの合計金額」なども書けます。

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Item {
    private final String name;
    private final int price;
    Item(String name, int price) {
        this.name = name;
        this.price = price;
    }
    int getPrice() { return price; }
}

public class PartitionSumDemo {
    public static void main(String[] args) {
        List<Item> items = List.of(
                new Item("A", 100),
                new Item("B", 500),
                new Item("C", 300),
                new Item("D", 800)
        );

        Map<Boolean, Integer> sumByHighPrice =
                items.stream()
                     .collect(Collectors.partitioningBy(
                             i -> i.getPrice() >= 300,
                             Collectors.summingInt(Item::getPrice)
                     ));

        System.out.println("高額商品の合計: " + sumByHighPrice.get(true));   // 1600
        System.out.println("低額商品の合計: " + sumByHighPrice.get(false));  // 100
    }
}
Java

「条件で二分」+「各グループで合計」も、partitioningBy と下流 Collector の組み合わせで素直に書けます。引用元: Java 逆引き集 | partitioningBy(true/false に分割) — 二分集計


設計視点:いつ partitioningBy を選ぶべきか

「二択」であることが本質なら partitioningBy 一択

partitioningBy を使うべき典型的な場面は、条件が本質的に「二択」になっているときです。

合格 / 不合格
アクティブ / 非アクティブ
エラーあり / エラーなし
偶数 / 奇数

こういうときに groupingBy を使うと、「Boolean をキーにした一般グルーピング」という、少し遠回りな表現になります。
一方 partitioningBy なら、「これは二分割なんだ」とコードからすぐに読み取れます。引用元: Collector応用 – partitioningBy, collectingAndThen, reducing の設計判断とレビュー視点

groupingBy との選択基準を自分の中に持つ

ざっくりとしたマイルールはこう置いておくと楽です。

キーが 3 種類以上になり得るなら groupingBy
キーが true / false の 2 種類だけなら partitioningBy

そして、「二分割した上でさらに集計したい」なら、
partitioningBy(条件, counting / summing / mapping ...) の形をまず検討する。

この感覚があると、Stream の出口設計がかなりスムーズになります。引用元: Collector応用 – partitioningBy, collectingAndThen, reducing の設計判断とレビュー視点


まとめ:partitioningBy を自分の言葉で定義する

あなたの言葉で partitioningBy をまとめるなら、こうなります。

partitioningBy は、Predicate で指定した条件に対して、Stream の要素を true / false の 2 グループにきっちり分ける Collector。
戻り値は Map<Boolean, …> で、partitioningBy(predicate) なら Map<Boolean, List<T>>partitioningBy(predicate, downstream) なら Map<Boolean, R> になる。
“二択”の条件で分けたいときに、groupingBy よりも意図がはっきり伝わる。」

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