技術と日常。

日々の気が付いたこと・気になったことを残しておきます。

[Java]StreamのgroupingByで、元の順序を保つ方法

groupingByを普通に書くと

例えば、あるStringのリストがあり、それぞれの個数を数えるケースを考えます。
普通に書くと以下のようになるかと思います。

Map<String, Long> collected = List.of("d", "c", "b", "a", "d", "c", "b", "d", "c", "d")
        .stream()
        .collect(Collectors.groupingBy(
                Function.identity(),
                Collectors.counting()
        ));
System.out.println(collected);

> Task :run
{a=1, b=2, c=3, d=4}

この場合、groupingByの結果は、Listの順序は保証されません。
これは、戻り値として使用されるMap自体が、順序が保証されていないためです。

LinkedHashMapを使う

そこで、引数を3つ取るgroupingByを使用します

public static <T, K, D, A, M extends Map<K, D>>
Collector<T,?,M> groupingBy(
        Function<? super T,? extends K> classifier,
        Supplier<M> mapFactory,
        Collector<? super T,A,D> downstream
)

第2引数に順序が保証されるLinkedHashMapSupplierを渡すことで、順序が保たれます。

Map<String, Long> collected = List.of("d", "c", "b", "a", "d", "c", "b", "d", "c", "d")
        .stream()
        .collect(Collectors.groupingBy(
                Function.identity(),
                LinkedHashMap::new,
                Collectors.counting()
        ));
System.out.println(collected);

> Task :run
{d=4, c=3, b=2, a=1}

2引数のgroupingByの実装

参考までに、2引数のgroupingByの実装を見ると、内部では3引数のgroupingByに、順序の保たれないHashMapを渡していることがわかります。

public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                      Collector<? super T, A, D> downstream) {
    return groupingBy(classifier, HashMap::new, downstream);
}

参考

Collectors (Java SE 17 & JDK 17)

java - Stream doesn't preserve the order after grouping - Stack Overflow