Stream.forEach()でMapを作ってはいけない。

toMap()

toMap()は、Mapのインスタンスを生成する。
そのとき、キーと値をそれぞれ生成するためのFunctionを指定する。

シグニチャ
public static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,
                                                    Function<? super T,? extends U> valueMapper)
toMap()の例
Map<Integer, String> expected = new HashMap<>();
expected.put(1, "1");
expected.put(2, "2");
expected.put(3, "3");
 
Integer[] numbers = { 1, 2, 3 };
Stream<Integer> stream = Arrays.stream(numbers);
Map<Integer, String> actual = stream.collect(Collectors.toMap(i -> i, String::valueOf));
assertThat(actual, is(expected));

キー重複の対処

Mapを生成するときに気をつけなくはいけないのが
キーの重複。

2つの引数をとるtoMapではキーの重複が発生すると
例外となってしまう。

× キーが重複するのでIllegalStateExceptionになる
Integer[] numbers = { 1, 2, 2 };
Stream<Integer> stream = Arrays.stream(numbers);
stream.collect(Collectors.toMap(i -> i, String::valueOf));

これはメソッドの定義の問題。
2つの引数をとるtoMap()では
キー重複が発生すると例外にする用に定義されている。

もちろんキーの重複があっても、
Mapは生成することは当然可能。

mergerを指定するtoMap()を用いて、
自分でmergerを設定すれば回避できる。

シグニチャ
public static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,
                                                    Function<? super T,? extends U> valueMapper,
                                                    BinaryOperator<U> mergeFunction)

PR