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

CollectorsではCollectionインスタンスに
リダイレクトするためのCollectorが3つ用意されている。

メソッド 概要
toList() ListのインスタンスにするためのCollectorを返す。
インスタンスは、実装で決定される。
toSet() SetのインスタンスにするためのCollectorを返す。
インスタンスは、実装で決定される。
toCollection() CollectionのインスタンスにするためのCollectorを返す。
インスタンスは、supplierで指定する。

StreamからforEach()でコレクションを作成することもできるが
バグの原因にもなるのでやってはいけない。

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

Stream処理後にListやMapにしたいことがある。

このときに(JDK7)以前の思考で、
forEachを利用して生成したいと考えてしまうが、
これをやってはいけない。

×よくない例
Integer[] numbers = { 1, 2, 3, 4 };
Stream<Integer> stream = Arrays.stream(numbers);
 
List<Integer> actual = new ArrayList<>();
stream.forEach(i -> {
    actual.add(i);
});

これ自体は動作するが、
例えばparallelストリームの場合はバグである。
(ArrayListはスレッドセーフではないため)

こういった場合は、CollectorsのtoCollection()やtoMap()を用いる。

toList() toSet()

toList()とtoSet()はそれぞれ、ListとSetのインスタンスに変換する。
(実装によるが)ArrayListとHashSetのインスタンスになる。

toList()の例
List<Integer> expected = Arrays.asList(1, 2, 3, 4);
 
Integer[] numbers = { 1, 2, 3, 4 };
Stream<Integer> stream = Arrays.stream(numbers);
List<Integer> actual = stream.collect(Collectors.toList());
assertThat(actual, is(expected));

Setインスタンス生成時は注意が必要で、
重複をチェックするためにequals()が評価されるため
大量件数ではparallel()であってもスピードが出ない場合がある。

toCollection()

ArrayListやHashSetではなく
同期に対応したCollectionのインスタンスにしたい場合は
toCollection()を利用する。

シグニチャ
toCollection(Supplier<C> collectionFactory)

toCollection()では、
supplierで生成するCollectionのインスタンスを指定できる。

toCollection()の例
List<Integer> expected = Arrays.asList(1, 2, 3, 4);
 
Integer[] numbers = { 1, 2, 3, 4 };
Stream<Integer> stream = Arrays.stream(numbers);
CopyOnWriteArrayList<Integer> actual = stream.collect(Collectors.toCollection(CopyOnWriteArrayList::new));
assertThat(actual, is(expected));

PR