Java8 Stream API 終端処理 (TerminalOperation)

java8 Stream APIの終端処理についてのメモです。

前回の続きで、Stream APIの終端処理について書きました。
pppurple.hatenablog.com

ストリーム生成⇒中間処理⇒終端処理の終端処理の部分です。

目次

終端処理

count

countはストリームの要素をカウントし、long型で返します。

@Test
public void count() {
    long count = IntStream.rangeClosed(1, 10)
            .filter(i -> i > 5)
            .count();
    assertThat(count).isEqualTo(5);
}

min

minはストリームから最小の要素を返します。

IntStream、LongStream、DoubleStreamの場合、自然順序で最小値を返します。
戻り値はそれぞれ、OptionalInt、OptionalLong、OptionalDoubleになります。

Streamの場合は、引数で与えたComparatorに従って最小値を返します。
戻り値はOptionalになります。

@Test
public void min() {
    // int
    OptionalInt min = IntStream.of(3, 5, 2, 8, 4)
            .min();
    assertThat(min.getAsInt()).isEqualTo(2);

    // String
    Optional<String> minText = Stream.of("aaa", "bbb", "ccc")
            .min(Comparator.naturalOrder());
    assertThat(minText.get()).isEqualTo("aaa");
}

max

maxはストリームから最大の要素を返します。

IntStream、LongStream、DoubleStreamの場合、自然順序で最大値を返します。
戻り値はそれぞれ、OptionalInt、OptionalLong、OptionalDoubleになります。

Streamの場合は、引数で与えたComparatorに従って最大値を返します。
戻り値はOptionalになります。

@Test
public void max() {
    // int
    OptionalInt max = IntStream.of(3, 5, 2, 8, 4)
            .max();
    assertThat(max.getAsInt()).isEqualTo(8);

    // String
    Optional<String> maxText = Stream.of("aaa", "bbb", "ccc")
            .max(Comparator.naturalOrder());
    assertThat(maxText.get()).isEqualTo("ccc");
}

sum

sumはストリームの要素の合計を返します。
戻り値はIntStream、LongStream、DoubleStreamでそれぞれ、int、long、doubleになります。

@Test
public void sum() {
    int sum = IntStream.rangeClosed(1, 10)
            .sum();
    assertThat(sum).isEqualTo(55);
}

average

averageはストリームの要素の平均を返します。
戻り値はOpotionalDoubleになります。

@Test
public void average() {
    OptionalDouble avg = IntStream.rangeClosed(1, 10)
            .average();
    assertThat(avg.getAsDouble()).isEqualTo(5.5);
}

summaryStatistics

summaryStatisticsはIntSummaryStatisticsを返します。
IntSummaryStatisticsはカウント数、最小、最大、合計、平均などの統計情報を取得できます。

@Test
public void summaryStatistics() {
    IntSummaryStatistics summary = IntStream.rangeClosed(1, 10)
            .summaryStatistics();
    assertThat(summary.getMax()).isEqualTo(10);
    assertThat(summary.getMin()).isEqualTo(1);
    assertThat(summary.getAverage()).isEqualTo(5.5);
    assertThat(summary.getCount()).isEqualTo(10);
    assertThat(summary.getSum()).isEqualTo(55);
}

findFirst

findFirstはストリームの最初の要素を返します。
戻り値はOptionalになります。
filterなどでフィルタリングされたストリームといっしょに使われます。

@Test
public void findFirst() {
    Optional<String> first = Stream.of("a", "aa", "aaa")
            .filter(s -> s.length() > 1)
            .findFirst();
    assertThat(first.get()).isEqualTo("aa");
}

findAny

findAnyはストリームの1つの要素を返します。
パフォーマンスのために、findFirstと違い最初の要素を返すわけではありません。
そのため同一の結果は保障されません。

static List<String> aList = Arrays.asList("aa", "aaa");
Condition<String> aCondtion = new Condition<>(aList::contains, "a match");

@Test
public void findAny() {
    Optional<String> any = Stream.of("a", "aa", "aaa")
            .filter(s -> s.length() > 1)
            .findAny();
    assertThat(any.get()).is(aCondtion);
}

anyMatch

anyMatchはストリームのいずれかの要素が引数で指定した条件に一致するかを
booleanで返します。

@Test
public void anyMatch() {
    boolean matchAny = IntStream.rangeClosed(1, 10)
            .anyMatch(i -> i > 8);
    assertThat(matchAny).isTrue();

    boolean matchAny2 = IntStream.rangeClosed(1, 10)
            .anyMatch(i -> i > 10);
    assertThat(matchAny2).isFalse();
}

allMatch

allMatchはストリームのすべての要素が引数で指定した条件に一致するかを
booleanで返します。

@Test
public void allMatch() {
    boolean matchAll = IntStream.rangeClosed(1, 10)
            .allMatch(i -> i > 0);
    assertThat(matchAll).isTrue();

    boolean matchAll2 = IntStream.rangeClosed(1, 10)
            .allMatch(i -> i > 8);
    assertThat(matchAll2).isFalse();
}

noneMatch

noneMatchはストリームのいずれの要素も引数で指定した条件に一致しないかを
booleanで返します。

@Test
public void noneMatch() {
    boolean matchNone = IntStream.rangeClosed(1, 10)
            .noneMatch(i -> i > 10);
    assertThat(matchNone).isTrue();

    boolean matchNone2 = IntStream.rangeClosed(1, 10)
            .noneMatch(i -> i > 8);
    assertThat(matchNone2).isFalse();
}

reduce(identity, accumulator)

reduce(T identity, BinaryOperator<T> accumulator)はストリームの各要素に対して、accumulatorで指定した関数を適用します。
accumulatorはBinaryOperatorで第1引数に前回の処理結果、第2引数にストリームの要素となります。
一番最初の処理の場合、前回の処理結果がないため、単位元identityで指定した値を初期値として使用します。

@Test
public void reduceWithIdentity() {
    // reduce(T identity, BinaryOperator<T> accumulator)
    int sum = IntStream.rangeClosed(1, 10)
            .reduce(1, (a, b) -> a + b);
    assertThat(sum).isEqualTo(56);

    String text = Stream.of("aaa", "bbb", "ccc")
            .reduce("#", (a, b) -> a + b);
    assertThat(text).isEqualTo("#aaabbbccc");
}

reduce(accumulator)

reduce(BinaryOperator<T> accumulator)は、reduce(T identity, BinaryOperator<T> accumulator)の
単位元indentityがないメソッドです。
そのためaccumulatorの処理は、ストリームの第1要素と第2要素の処理から始まります。

戻り値はOptionalになります。reduce(T identity, BinaryOperator<T> accumulator)の場合は単位元を指定しているので、
ストリームの要素がない場合もnullになることはありませんが、
reduce(BinaryOperator<T> accumulator)は単位元がないため、ストリームの要素がない場合、
nullとなる可能性があるためです。

@Test
public void reduce() {
    // reduce(BinaryOperator<T> accumulator)
    OptionalInt sum = IntStream.rangeClosed(1, 10)
            .reduce((a, b) -> a + b);
    assertThat(sum.getAsInt()).isEqualTo(55);

    Optional<String> text = Stream.of("aaa", "bbb", "ccc")
            .reduce((a, b) -> a + b);
    assertThat(text.get()).isEqualTo("aaabbbccc");
}

reduce(identity, accumulator, combiner)

reduce(T identity, BinaryOperator<T> accumulator)は関数がBinaryOperatorになっているので、
ストリームの要素と戻り値は同じ型になります。

ストリームの要素と結果を異なる型にしたい場合、
reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)を使用します。
単位元identityの指定は同じで、処理accumulatorはBiFunctionになっているので、何らかの型を返す関数を記述します。
第3引数のcombinerですが、これはパラレルストリームの際に処理結果を集約するために使用します。
そのためBinaryOperatorになっており、これはaccumulatorの戻り値と同じ型になります。

StreamをMapに変換する場合。

@Test
public void reduceWithCombiner() {
    // reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
    Map<Integer, String> map = Stream.of("a", "bb", "ccc", "dd")
            .reduce(new HashMap<>(),
                    (m, s) -> {
                        m.put(s.length(), s);
                        return m;
                    },
                    (m1, m2) -> {
                        m1.putAll(m2);
                        return m1;
                    });

    map.keySet().forEach(k -> System.out.println(k + ":" + map.get(k)));

    assertThat(map).containsOnly(entry(1, "a"),
            entry(2, "dd"),
            entry(3, "ccc"));
}

toArray

toArrayはストリームの要素を配列で返します。

@Test
public void toArray() {
    int[] ints = IntStream.rangeClosed(1, 5)
            .toArray();
    assertThat(ints).containsSequence(1, 2, 3, 4, 5);

    String[] texts = Stream.of("aaa", "bbb", "ccc")
            .toArray(String[]::new);
    assertThat(texts).containsSequence("aaa", "bbb", "ccc");
}

iterator

iteratorはストリームの要素のイテレータを返します。

@Test
public void iterator() {
    PrimitiveIterator.OfInt ite = IntStream.rangeClosed(1, 10)
            .iterator();

    while (ite.hasNext()) {
        System.out.println(ite.next());
    }
}

spliterator

spliteratorはストリームの要素のスプリッテレータを返します。

@Test
public void spliterator() {
    Spliterator.OfInt spliterator = IntStream.of(3, 5, 2, 8, 4, 10, 6, 2, 8)
            .spliterator();

    assertThat(spliterator.hasCharacteristics(Spliterator.SORTED)).isFalse();

    Spliterator.OfInt orderdSpliterator = IntStream.of(3, 5, 2, 8, 4, 10, 6, 2, 8)
            .sorted()
            .spliterator();

    assertThat(orderdSpliterator.hasCharacteristics(Spliterator.SORTED)).isTrue();

    IntStream intStream = StreamSupport.intStream(spliterator, false);
    List<Integer> list = intStream.distinct()
            .sorted()
            .boxed()
            .collect(Collectors.toList());

    List<Integer> expected = Arrays.asList(2, 3, 4, 5, 6, 8, 10);
    assertThat(list).isEqualTo(expected);
}

forEach

forEachはConsumerインターフェースを引数にとるため、結果を返さない処理を記述します。

@Test
public void forEach() {
    IntStream.rangeClosed(1, 10)
            .forEach(System.out::println);
}

結果

1
2
3
4
5
6
7
8
9
10

forEachOrdered

forEachは順序を保証しませんが、forEachOrderedでは順序付けされている場合、順序が保証されます。

@Test
public void forEachOrdered() {
    // forEach
    IntStream.rangeClosed(1, 10)
            .parallel()
            .forEach(System.out::println);

    // forEachOrdered
    IntStream.rangeClosed(1, 10)
            .parallel()
            .forEachOrdered(System.out::println);
}

結果

// forEach
7
6
2
9
3
1
4
8
5
10

// forEachOrdered
1
2
3
4
5
6
7
8
9
10

collect(supplier, accumulator, combiner)

collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)は
reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)とほとんど同じです。

supplierで戻り値となる型を生成します。
accumulatorでストリームの処理を記述します。第1引数でsupplier、第2引数でストリームの要素を取ります。
reduceと異なるのはBiConsumerとなっている点で、戻り値は不要で、supplierへの処理が引き継がれます。
combinerはパラレルストリームで使用される集約処理です。
これもreduceと異なり、BiConcumerになっており、戻り値は不要で、集約処理を記述します。

StreamをMapに変換する場合。

@Test
public void collect() {
    Map<Integer, String> map = Stream.of("a", "bb", "ccc", "dd")
            .collect(HashMap::new,
                    (m, s) -> m.put(s.length(), s),
                    HashMap::putAll);

    assertThat(map).containsOnly(entry(1, "a"),
            entry(2, "dd"),
            entry(3, "ccc"));
}

collect(collector)

collect(Collector<? super T,A,R> collector)は、java.util.stream.Collectorインターフェースを取ります。
Collectorインターフェースの実装として、java.util.stream.Collectorsクラスがあり、便利なメソッドがたくさん定義されています。

下記にjava.util.stream.Collectorsクラスのメソッドを記載します。

counting

Collectors.countingはストリームの要素数を返します。
戻り値はLong型です。

@Test
public void counting() {
    Long count = IntStream.rangeClosed(1, 10)
            .boxed()
            .collect(Collectors.counting());
    assertThat(count).isEqualTo(10);

    Long countStr = Stream.of("a", "b", "c", "d")
            .collect(Collectors.counting());
    assertThat(countStr).isEqualTo(4);
}
minBy

Collectors.minByはストリームの最小要素を返します。
引数として比較のためのComparatorを指定します。
戻り値はOptional型となります。

@Test
public void minBy() {
    Integer min = Stream.of(2, 3, 8, 5, 6)
            .collect(Collectors.minBy(Integer::compare))
            .get();
    assertThat(min).isEqualTo(2);
}
maxBy

Collectors.maxByはストリームの最大要素を返します。
引数として比較のためのComparatorを指定します。
戻り値はOptional型となります。

@Test
public void maxBy() {
    Integer max = Stream.of(2, 3, 8, 5, 6)
            .collect(Collectors.maxBy(Integer::compare))
            .get();
    assertThat(max).isEqualTo(8);
}
summingInt

Collectors.summingIntは関数の処理結果のint値の合計を返します。
ToIntFunctionを引数に取るので、戻り値がintとなる関数を記述します。
戻り値はInteger型となります。

同様にlong値を合計するsummingLong
double値を合計するsummingDoubleがあります。

@Test
public void summingInt() {
    int sum = IntStream.rangeClosed(1, 10)
            .boxed()
            .collect(Collectors.summingInt(Integer::intValue));
    assertThat(sum).isEqualTo(55);

    int lengthSum = Stream.of("a", "bb", "ccc")
            .collect(Collectors.summingInt(String::length));
    assertThat(lengthSum).isEqualTo(6);
}
averagingInt

Collectors.averagingIntは関数の処理結果のint値の平均を返します。
ToIntFunctionを引数に取るので、戻り値がintとなる関数を記述します。
戻り値はDouble型となります。

同様に
 ・long値を平均するaveragingLong
 ・double値を平均するaveragingDouble
があります。
戻り値はすべてDouble型となります。

@Test
public void averagingInt() {
    double avg = IntStream.rangeClosed(1, 10)
            .boxed()
            .collect(Collectors.averagingInt(Integer::intValue));
    assertThat(avg).isEqualTo(5.5);

    double lengthAvg = Stream.of("a", "bb", "ccc")
            .collect(Collectors.averagingInt(String::length));
    assertThat(lengthAvg).isEqualTo(2.0);
}
summarizingInt

Collectors.summarizingIntは関数の処理結果のint値のサマリー統計を返します。
ToIntFunctionを引数に取るので、戻り値がintとなる関数を記述します。
戻り値はIntSummaryStatistics型となります。

IntSummaryStatisticsはカウント数、最小、最大、合計、平均などの統計情報を持つクラスです。
下記のメソッドを持っていて、統計情報を取得できます。

  • getAverage():平均を返す
  • getCount():値をカウントして返す
  • getMax():最大値を返す
  • getMin():最小値を返す
  • getSum():合計を返す

同様に
 ・LongSummaryStatisticsを返すsummarizingLong、
 ・DoubleSummaryStatisticsを返すsummarizingDouble
があります。

@Test
public void summarizingInt() {
    IntSummaryStatistics stat = IntStream.rangeClosed(1, 10)
            .boxed()
            .collect(Collectors.summarizingInt(Integer::intValue));
    int max = stat.getMax();
    int min = stat.getMin();
    double sum = stat.getSum();
    double avg = stat.getAverage();
    long count = stat.getCount();
    assertThat(max).isEqualTo(10);
    assertThat(min).isEqualTo(1);
    assertThat(sum).isEqualTo(55);
    assertThat(avg).isEqualTo(5.5);
    assertThat(count).isEqualTo(10);
}
joining

Collectors.joiningはストリームの要素を連結した文字列を返します。

@Test
public void joining() {
    // String
    String text = Stream.of("aaa", "bbb", "ccc")
            .map(String::toUpperCase)
            .collect(Collectors.joining());
    assertThat(text).isEqualTo("AAABBBCCC");

    // int
    String ints = IntStream.rangeClosed(1, 9)
            .mapToObj(String::valueOf)
            .collect(Collectors.joining());
    assertThat(ints).isEqualTo("123456789");
}
joining(delimiter)

Collectors.joining(CharSequence delimiter)はストリームの要素をdelimiterを区切り文字として連結した文字列を返します。

@Test
public void joiningWithDelimiter() {
    String csv = IntStream.rangeClosed(1, 9)
            .mapToObj(String::valueOf)
            .collect(Collectors.joining(","));
    assertThat(csv).isEqualTo("1,2,3,4,5,6,7,8,9");
}
joining(delimiter, prefix, suffix)

Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)は、
ストリームの要素をdelimiterを区切り文字として連結し、前後にprefixとsuffixを連結した文字列を返します。

@Test
public void joiningWithPrefixAndSuffix() {
    String csv = IntStream.rangeClosed(1, 9)
            .mapToObj(String::valueOf)
            .collect(Collectors.joining(",", "[", "]"));
    assertThat(csv).isEqualTo("[1,2,3,4,5,6,7,8,9]");
}
mapping

Collectors.mappingはストリームの要素を第1引数で処理し、結果を第2引数のCollectorで集約します。
第1引数はFunctionインターフェースなので、map()のようにマッピング処理を行います。
結果は第2引数でCollectorインターフェースでの処理(Collectorsクラスの処理)を記述します。

@Test
public void mapping() {
    // Collectors.mapping
    List<String> upperTextMapping = Stream.of("aaa", "bbb", "ccc")
            .collect(Collectors.mapping(String::toUpperCase,
                    Collectors.toList()));
    assertThat(upperTextMapping).containsSequence("AAA", "BBB", "CCC");
    
    // map()
    List<String> upperText = Stream.of("aaa", "bbb", "ccc")
            .map(String::toUpperCase)
            .collect(Collectors.toList());
    assertThat(upperText).containsSequence("AAA", "BBB", "CCC");
}
reducing(identity, accumulator)

Collectors.reducing(T identity, BinaryOperator<T> accumulator)はストリームの各要素に対して、
accumulatorで指定した関数を適用します。
accumulatorはBinaryOperatorで第1引数に前回の処理結果、第2引数にストリームの要素となります。
一番最初の処理の場合、前回の処理結果がないため、単位元identityで指定した値を初期値として使用します。

@Test
public void reducingWithIdentity() {
    // Collectors.reducing(T identity, BinaryOperator<T> op)
    int sumReducing = IntStream.rangeClosed(1, 10)
            .boxed()
            .collect(Collectors.reducing(1, (sum, i) -> sum + i));
    assertThat(sumReducing).isEqualTo(56);

    String text = Stream.of("aaa", "bbb", "ccc")
            .collect(Collectors.reducing("#", (a, b) -> a + b));
    assertThat(text).isEqualTo("#aaabbbccc");
}
reducing(accumulator)

Collectors.reducing(BinaryOperator<T> accumulator)は、Collectors.reduce(T identity, BinaryOperator<T> accumulator)の
単位元indentityがないメソッドです。
そのためaccumulatorの処理は、ストリームの第1要素と第2要素の処理から始まります。

戻り値はOptionalになります。Collectors.reduce(T identity, BinaryOperator<T> accumulator)の場合は単位元を指定しているので、
ストリームの要素がない場合もnullになることはありませんが、
Collectors.reduce(BinaryOperator<T> accumulator)は単位元がないため、ストリームの要素がない場合、
nullとなる可能性があるためです。

@Test
public void reducing() {
    // Collectors.reducing(BinaryOperator<T> op)
    Optional<String> text = Stream.of("aaa", "bbb", "ccc")
            .collect(Collectors.reducing((a, b) -> a + b));
    assertThat(text.get()).isEqualTo("aaabbbccc");
}
reducing(identity, accumulator, combiner)

Collectors.reducing(T identity, BinaryOperator<T> accumulator)は
関数がBinaryOperatorになっているので、ストリームの要素と戻り値は同じ型になります。

ストリームの要素と結果を異なる型にしたい場合、
Collectors.reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)を使用します。
単位元identityの指定は同じで、処理accumulatorはBiFunctionになっているので、何らかの型を返す関数を記述します。
第3引数のcombinerですが、これはパラレルストリームの際に処理結果を集約するために使用します。
そのためBinaryOperatorになっており、これはaccumulatorの戻り値と同じ型になります。

@Test
public void reducingWithMapper() {
    // Collectors.reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)
    int sumLength = Stream.of("a", "bb", "ccc", "dd")
            .collect(Collectors.reducing(100,
                    String::length,
                    (a, b) -> a + b));

    assertThat(sumLength).isEqualTo(108);
}
groupingBy(classifier)

Collectors.groupingBy(Function<? super T,? extends K> classifier)は、classifierで指定した関数に従って
グルーピングした結果を返します。

@Test
public void groupingBy() {
    // Collectors.groupingBy(Function<? super T,? extends K> classifier)
    Map<Integer, List<String>> lengthMap =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.groupingBy(String::length));
    assertThat(lengthMap).containsOnlyKeys(1, 2, 3);
    assertThat(lengthMap).contains(entry(1, Arrays.asList("a", "d")));
    assertThat(lengthMap).contains(entry(2, Arrays.asList("bb", "ee")));
    assertThat(lengthMap).contains(entry(3, Arrays.asList("ccc", "fff")));
    assertThat(lengthMap).containsOnly(entry(1, Arrays.asList("a", "d")),
            entry(2, Arrays.asList("bb", "ee")),
            entry(3, Arrays.asList("ccc", "fff")));
}
groupingBy(classifier, downstream)

Collectors.groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)は、
classifierで指定した関数に従ってグルーピングした結果に対し、
downstreamで指定したCollectorインターフェースを実装した関数(Collectorsクラスのメソッド)を適用します。

@Test
public void groupingByWithDownstream() {
    // Collectors.groupingBy(Function<? super T,? extends K> classifier,
    //                       Collector<? super T,A,D> downstream)
    Map<Integer, Long> countLength =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.groupingBy(String::length,
                            Collectors.counting()));
    assertThat(countLength).containsOnly(entry(1, 2L), entry(2, 2L), entry(3, 2L));
}
groupingBy(classifier, mapFactory, downstream)

Collectors.groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)は、
classifierで指定した関数に従ってグルーピングした結果に対し、
downstreamで指定したCollectorインターフェースを実装した関数(Collectorsクラスのメソッド)を適用した結果をMapに格納します。

@Test
public void groupingByWithMapFactory() {
    // Collectors.groupingBy(Function<? super T,? extends K> classifier,
    //                       Supplier<M> mapFactory,
    //                       Collector<? super T,A,D> downstream)
    Map<String, Long> stringCount =
            Stream.of("a", "bb", "ccc", "A", "dd", "CCC")
                    .collect(Collectors.groupingBy(String::toUpperCase,
                            TreeMap::new,
                            Collectors.counting()));
    assertThat(stringCount).containsOnlyKeys("A", "BB", "CCC", "DD");
    assertThat(stringCount).containsValues(2L, 1L, 2L, 1L);
}
groupingByConcurrent(classifier)

Collectors.groupingByConcurrent(Function<? super T,? extends K> classifier)は、
Collectors.groupingBy(Function<? super T,? extends K> classifier)と同じ引数を取りますが、ConcurrentMapを返します。

@Test
public void groupingByConcurrent() {
    // groupingByConcurrent(Function<? super T,? extends K> classifier,
    //                      Collector<? super T,A,D> downstream)
    ConcurrentMap<Integer, List<String>> lengthMap =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.groupingByConcurrent(String::length));
    assertThat(lengthMap).containsOnlyKeys(1, 2, 3);
    assertThat(lengthMap).contains(entry(1, Arrays.asList("a", "d")));
    assertThat(lengthMap).contains(entry(2, Arrays.asList("bb", "ee")));
    assertThat(lengthMap).contains(entry(3, Arrays.asList("ccc", "fff")));
    assertThat(lengthMap).containsOnly(entry(1, Arrays.asList("a", "d")),
            entry(2, Arrays.asList("bb", "ee")),
            entry(3, Arrays.asList("ccc", "fff")));
}
groupingByConcurrent(classifier, downstream)

Collectors.groupingByConcurrent(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)は、
Collectors.groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)と同じ引数を取りますが、
ConcurrentMapを返します。

@Test
public void groupingByConcurrentWithDownstream() {
    ConcurrentMap<Integer, Long> countLength =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.groupingByConcurrent(String::length,
                            Collectors.counting()));
    assertThat(countLength).containsOnly(entry(1, 2L), entry(2, 2L), entry(3, 2L));
}
groupingByConcurrent(classifier, mapFactory, downstream)

Collectors.groupingByConcurrent(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)は、
Collectors.groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)と同じ引数を取りますが、ConcurrentMapを返します。

@Test
public void groupingByConcurrentWithMapFactory() {
    ConcurrentMap<String, Long> stringCount =
            Stream.of("a", "bb", "ccc", "A", "dd", "CCC")
                    .collect(Collectors.groupingByConcurrent(String::toUpperCase,
                            ConcurrentSkipListMap::new,
                            Collectors.counting()));
    assertThat(stringCount).containsOnlyKeys("A", "BB", "CCC", "DD");
    assertThat(stringCount).containsValues(2L, 1L, 2L, 1L);
}
partitioningBy(predicate)

Collectors.partitioningBy(Predicate<? super T> predicate)は、predicateで指定した条件の結果をMapに格納します。
Predicateインターフェースはbooleanを返す関数を記述します。結果がtrueのものと、falseのものでそれぞれMapに格納されます。

@Test
public void partitioningBy() {
    Map<Boolean, List<String>> length3 =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.partitioningBy(s -> s.length() > 2));
    assertThat(length3).contains(entry(true, Arrays.asList("ccc", "fff")));
    assertThat(length3).contains(entry(false, Arrays.asList("a", "bb", "d", "ee")));
}
partitioningBy(predicate, downstream)

Collectors.partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream)は、
predicateで指定した条件の結果に対して、downstreamで指定した関数を適用した結果をMapに格納します。
Predicateインターフェースはbooleanを返す関数を記述します。結果がtrueのものと、falseのものでそれぞれMapに格納されます。
downstreamはCollectorインターフェースを実装した関数(Collectorsクラスのメソッド)を記述します。

@Test
public void partitioningByWithDownstream() {
    Map<Boolean, Long> length3 =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.partitioningBy(s -> s.length() > 2,
                            Collectors.counting()));
    assertThat(length3).contains(entry(true, 2L));
    assertThat(length3).contains(entry(false, 4L));
}
collectingAndThen

Collectors.collectingAndThenはcollect処理した結果に後処理を行います。
第1引数でcollect処理を行い、第2引数ではFunctionインターフェースを取る後処理を記述します。

@Test
public void collectingAndThen() {
    List<String> list = Stream.of("a", "b", "c", "d", "e")
            .map(String::toUpperCase)
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
    assertThat(list).containsOnly("A", "B", "C", "D", "E");
}
toCollection

Collectors.toCollectionはストリームの要素を含むコレクションを返します。
引数としてコレクションを生成する処理を記述します。

@Test
public void toCollection() {
    List<Integer> list = IntStream.rangeClosed(1, 5)
            .boxed()
            .collect(Collectors.toCollection(LinkedList::new));
    assertThat(list).containsOnly(1, 2, 3, 4, 5);
}
toList

Collectors.toListはストリームの要素をListで返します。

@Test
public void toList() {
    // int
    List<Integer> list = IntStream.rangeClosed(1, 5)
            .map(i -> i + 100)
            .boxed()
            .collect(Collectors.toList());
    assertThat(list).containsSequence(101, 102, 103, 104, 105);

    // String
    List<String> text = Stream.of("aaa", "bbb", "ccc")
            .map(String::toUpperCase)
            .collect(Collectors.toList());
    assertThat(text).containsSequence("AAA", "BBB", "CCC");
}
toMap(keyMapper, valueMapper)

Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)は
ストリームの要素をMapで返します。
keyとvalueを得るためのFunctionインターフェースを実装した関数を記述します。

@Test
public void toMap() {
    // Collectors.toMap(Function<? super T,? extends K> keyMapper,
    //                  Function<? super T,? extends U> valueMapper)
    Map<String, String> upper =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.toMap(s -> s,
                            String::toUpperCase));
    assertThat(upper).contains(entry("a", "A"));
    assertThat(upper).contains(entry("bb", "BB"));
    assertThat(upper).contains(entry("ccc", "CCC"));
    assertThat(upper).contains(entry("d", "D"));
    assertThat(upper).contains(entry("ee", "EE"));
    assertThat(upper).contains(entry("fff", "FFF"));
}
toMap(keyMapper, valueMapper, mergeFunction)

Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)は
ストリームの要素をMapで返します。
keyとvalueを得るためのFunctionインターフェースを実装した関数を記述します。
マップのキーが重複する場合は、指定したmergeFunctionでvalueがマージされます。

@Test
public void toMapWithMerge() {
    // Collectors.toMap(Function<? super T,? extends K> keyMapper,
    //                  Function<? super T,? extends U> valueMapper,
    //                  BinaryOperator<U> mergeFunction)
    Map<Integer, String> upper =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.toMap(String::length,
                            s -> s,
                            (s1, s2) -> s1 + "," + s2));
    assertThat(upper).contains(entry(1, "a,d"));
    assertThat(upper).contains(entry(2, "bb,ee"));
    assertThat(upper).contains(entry(3, "ccc,fff"));
}
toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)

Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)は
ストリームの要素をMapで返します。
keyとvalueを得るためのFunctionインターフェースを実装した関数を記述します。
マップのキーが重複する場合は、指定したmergeFunctionでvalueがマージされます。
パラレルストリームで処理する場合、mapSupplierで指定した関数で統合されます。

@Test
public void toMapWithMapSupplier() {
    // Collectors.toMap(Function<? super T,? extends K> keyMapper,
    //                  Function<? super T,? extends U> valueMapper,
    //                  BinaryOperator<U> mergeFunction,
    //                  Supplier<M> mapSupplier)
    Map<Integer, String> upper =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.toMap(String::length,
                            s -> s,
                            (s1, s2) -> s1 + "," + s2,
                            HashMap::new));
    assertThat(upper).contains(entry(1, "a,d"));
    assertThat(upper).contains(entry(2, "bb,ee"));
    assertThat(upper).contains(entry(3, "ccc,fff"));
}
toConcurrentMap(keyMapper, valueMapper)

Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)は、
Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)と引数は同じですが、
ConcurrentMapを返します。

@Test
public void toConcurrentMap() {
    // Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper,
    //                            Function<? super T,? extends U> valueMapper)
    ConcurrentMap<String, String> upper =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.toConcurrentMap(s -> s,
                            String::toUpperCase));
    assertThat(upper).contains(entry("a", "A"));
    assertThat(upper).contains(entry("bb", "BB"));
    assertThat(upper).contains(entry("ccc", "CCC"));
    assertThat(upper).contains(entry("d", "D"));
    assertThat(upper).contains(entry("ee", "EE"));
    assertThat(upper).contains(entry("fff", "FFF"));
}
toConcurrentMap(keyMapper, valueMapper, mergeFunction)

Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)は、
Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)と引数は同じですが、ConcurrentMapを返します。

@Test
public void toConcurrentMapWithMerge() {
    // Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper,
    //                            Function<? super T,? extends U> valueMapper,
    //                            BinaryOperator<U> mergeFunction)
    ConcurrentMap<Integer, String> upper =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.toConcurrentMap(String::length,
                            s -> s,
                            (s1, s2) -> s1 + "," + s2));
    assertThat(upper).contains(entry(1, "a,d"));
    assertThat(upper).contains(entry(2, "bb,ee"));
    assertThat(upper).contains(entry(3, "ccc,fff"));
}
toConcurrentMap(keyMapper, valueMapper, mergeFunction, mapSupplier)

Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)は
Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)と引数は同じですが、ConcurrentMapを返します。

@Test
public void toConcurrentMapWithMapSupplier() {
    // Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper,
    //                            Function<? super T,? extends U> valueMapper,
    //                            BinaryOperator<U> mergeFunction,
    //                            Supplier<M> mapSupplier)
    ConcurrentMap<Integer, String> upper =
            Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                    .collect(Collectors.toConcurrentMap(String::length,
                            s -> s,
                            (s1, s2) -> s1 + "," + s2,
                            ConcurrentHashMap::new));
    assertThat(upper).contains(entry(1, "a,d"));
    assertThat(upper).contains(entry(2, "bb,ee"));
    assertThat(upper).contains(entry(3, "ccc,fff"));
}
toSet

Collectors.toSetはストリームの要素からSetを生成します。

@Test
public void toSet() {
    Set<String> set = Stream.of("a", "b", "c", "b", "d")
            .collect(Collectors.toSet());
    assertThat(set).containsOnly("a", "b", "c", "d");
}

テストコード

今回のテストコードの全体です。

package javase8;

import org.assertj.core.api.Condition;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IntSummaryStatistics;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.PrimitiveIterator;
import java.util.Set;
import java.util.Spliterator;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

public class StreamApiTerminalOperationTest {
    @Test
    public void count() {
        long count = IntStream.rangeClosed(1, 10)
                .filter(i -> i > 5)
                .count();
        assertThat(count).isEqualTo(5);
    }

    @Test
    public void min() {
        // int
        OptionalInt min = IntStream.of(3, 5, 2, 8, 4)
                .min();
        assertThat(min.getAsInt()).isEqualTo(2);

        // String
        Optional<String> minText = Stream.of("aaa", "bbb", "ccc")
                .min(Comparator.naturalOrder());
        assertThat(minText.get()).isEqualTo("aaa");
    }

    @Test
    public void max() {
        // int
        OptionalInt max = IntStream.of(3, 5, 2, 8, 4)
                .max();
        assertThat(max.getAsInt()).isEqualTo(8);

        // String
        Optional<String> maxText = Stream.of("aaa", "bbb", "ccc")
                .max(Comparator.naturalOrder());
        assertThat(maxText.get()).isEqualTo("ccc");
    }

    @Test
    public void sum() {
        int sum = IntStream.rangeClosed(1, 10)
                .sum();
        assertThat(sum).isEqualTo(55);
    }

    @Test
    public void average() {
        OptionalDouble avg = IntStream.rangeClosed(1, 10)
                .average();
        assertThat(avg.getAsDouble()).isEqualTo(5.5);
    }

    @Test
    public void summaryStatistics() {
        IntSummaryStatistics summary = IntStream.rangeClosed(1, 10)
                .summaryStatistics();
        assertThat(summary.getMax()).isEqualTo(10);
        assertThat(summary.getMin()).isEqualTo(1);
        assertThat(summary.getAverage()).isEqualTo(5.5);
        assertThat(summary.getCount()).isEqualTo(10);
        assertThat(summary.getSum()).isEqualTo(55);
    }

    @Test
    public void findFirst() {
        Optional<String> first = Stream.of("a", "aa", "aaa")
                .filter(s -> s.length() > 1)
                .findFirst();
        assertThat(first.get()).isEqualTo("aa");
    }

    static List<String> aList = Arrays.asList("aa", "aaa");
    Condition<String> aCondtion = new Condition<>(aList::contains, "a match");

    @Test
    public void findAny() {
        Optional<String> any = Stream.of("a", "aa", "aaa")
                .filter(s -> s.length() > 1)
                .findAny();
        assertThat(any.get()).is(aCondtion);
    }

    @Test
    public void anyMatch() {
        boolean matchAny = IntStream.rangeClosed(1, 10)
                .anyMatch(i -> i > 8);
        assertThat(matchAny).isTrue();

        boolean matchAny2 = IntStream.rangeClosed(1, 10)
                .anyMatch(i -> i > 10);
        assertThat(matchAny2).isFalse();
    }

    @Test
    public void allMatch() {
        boolean matchAll = IntStream.rangeClosed(1, 10)
                .allMatch(i -> i > 0);
        assertThat(matchAll).isTrue();

        boolean matchAll2 = IntStream.rangeClosed(1, 10)
                .allMatch(i -> i > 8);
        assertThat(matchAll2).isFalse();
    }

    @Test
    public void noneMatch() {
        boolean matchNone = IntStream.rangeClosed(1, 10)
                .noneMatch(i -> i > 10);
        assertThat(matchNone).isTrue();

        boolean matchNone2 = IntStream.rangeClosed(1, 10)
                .noneMatch(i -> i > 8);
        assertThat(matchNone2).isFalse();
    }

    @Test
    public void reduceWithIdentity() {
        // reduce(T identity, BinaryOperator<T> accumulator)
        int sum = IntStream.rangeClosed(1, 10)
                .reduce(1, (a, b) -> a + b);
        assertThat(sum).isEqualTo(56);

        String text = Stream.of("aaa", "bbb", "ccc")
                .reduce("#", (a, b) -> a + b);
        assertThat(text).isEqualTo("#aaabbbccc");
    }

    @Test
    public void reduce() {
        // reduce(BinaryOperator<T> accumulator)
        OptionalInt sum = IntStream.rangeClosed(1, 10)
                .reduce((a, b) -> a + b);
        assertThat(sum.getAsInt()).isEqualTo(55);

        Optional<String> text = Stream.of("aaa", "bbb", "ccc")
                .reduce((a, b) -> a + b);
        assertThat(text.get()).isEqualTo("aaabbbccc");
    }

    @Test
    public void reduceWithCombiner() {
        // reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
        Map<Integer, String> map = Stream.of("a", "bb", "ccc", "dd")
                .reduce(new HashMap<>(),
                        (m, s) -> {
                            m.put(s.length(), s);
                            return m;
                        },
                        (m1, m2) -> {
                            m1.putAll(m2);
                            return m1;
                        });

        map.keySet().forEach(k -> System.out.println(k + ":" + map.get(k)));

        assertThat(map).containsOnly(entry(1, "a"),
                entry(2, "dd"),
                entry(3, "ccc"));
    }

    @Test
    public void toArray() {
        int[] ints = IntStream.rangeClosed(1, 5)
                .toArray();
        assertThat(ints).containsSequence(1, 2, 3, 4, 5);

        String[] texts = Stream.of("aaa", "bbb", "ccc")
                .toArray(String[]::new);
        assertThat(texts).containsSequence("aaa", "bbb", "ccc");
    }

    @Test
    public void iterator() {
        PrimitiveIterator.OfInt ite = IntStream.rangeClosed(1, 10)
                .iterator();

        while (ite.hasNext()) {
            System.out.println(ite.next());
        }
    }

    @Test
    public void spliterator() {
        Spliterator.OfInt spliterator = IntStream.of(3, 5, 2, 8, 4, 10, 6, 2, 8)
                .spliterator();

        assertThat(spliterator.hasCharacteristics(Spliterator.SORTED)).isFalse();

        Spliterator.OfInt orderdSpliterator = IntStream.of(3, 5, 2, 8, 4, 10, 6, 2, 8)
                .sorted()
                .spliterator();

        assertThat(orderdSpliterator.hasCharacteristics(Spliterator.SORTED)).isTrue();

        IntStream intStream = StreamSupport.intStream(spliterator, false);
        List<Integer> list = intStream.distinct()
                .sorted()
                .boxed()
                .collect(Collectors.toList());

        List<Integer> expected = Arrays.asList(2, 3, 4, 5, 6, 8, 10);
        assertThat(list).isEqualTo(expected);
    }

    @Test
    public void forEach() {
        IntStream.rangeClosed(1, 10)
                .forEach(System.out::println);
    }

    @Test
    public void forEachOrdered() {
        // forEach
        IntStream.rangeClosed(1, 10)
                .parallel()
                .forEach(System.out::println);

        // forEachOrdered
        IntStream.rangeClosed(1, 10)
                .parallel()
                .forEachOrdered(System.out::println);
    }

    @Test
    public void collect() {
        Map<Integer, String> map = Stream.of("a", "bb", "ccc", "dd")
                .collect(HashMap::new,
                        (m, s) -> m.put(s.length(), s),
                        HashMap::putAll);

        assertThat(map).containsOnly(entry(1, "a"),
                entry(2, "dd"),
                entry(3, "ccc"));
    }

    @Test
    public void counting() {
        Long count = IntStream.rangeClosed(1, 10)
                .boxed()
                .collect(Collectors.counting());
        assertThat(count).isEqualTo(10);

        Long countStr = Stream.of("a", "b", "c", "d")
                .collect(Collectors.counting());
        assertThat(countStr).isEqualTo(4);
    }

    @Test
    public void minBy() {
        Integer min = Stream.of(2, 3, 8, 5, 6)
                .collect(Collectors.minBy(Integer::compare))
                .get();
        assertThat(min).isEqualTo(2);
    }

    @Test
    public void maxBy() {
        Integer max = Stream.of(2, 3, 8, 5, 6)
                .collect(Collectors.maxBy(Integer::compare))
                .get();
        assertThat(max).isEqualTo(8);
    }

    @Test
    public void summingInt() {
        int sum = IntStream.rangeClosed(1, 10)
                .boxed()
                .collect(Collectors.summingInt(Integer::intValue));
        assertThat(sum).isEqualTo(55);

        int lengthSum = Stream.of("a", "bb", "ccc")
                .collect(Collectors.summingInt(String::length));
        assertThat(lengthSum).isEqualTo(6);
    }

    @Test
    public void averagingInt() {
        double avg = IntStream.rangeClosed(1, 10)
                .boxed()
                .collect(Collectors.averagingInt(Integer::intValue));
        assertThat(avg).isEqualTo(5.5);

        double lengthAvg = Stream.of("a", "bb", "ccc")
                .collect(Collectors.averagingInt(String::length));
        assertThat(lengthAvg).isEqualTo(2.0);
    }

    @Test
    public void summarizingInt() {
        IntSummaryStatistics stat = IntStream.rangeClosed(1, 10)
                .boxed()
                .collect(Collectors.summarizingInt(Integer::intValue));
        int max = stat.getMax();
        int min = stat.getMin();
        double sum = stat.getSum();
        double avg = stat.getAverage();
        long count = stat.getCount();
        assertThat(max).isEqualTo(10);
        assertThat(min).isEqualTo(1);
        assertThat(sum).isEqualTo(55);
        assertThat(avg).isEqualTo(5.5);
        assertThat(count).isEqualTo(10);
    }

    @Test
    public void joining() {
        // String
        String text = Stream.of("aaa", "bbb", "ccc")
                .map(String::toUpperCase)
                .collect(Collectors.joining());
        assertThat(text).isEqualTo("AAABBBCCC");

        // int
        String ints = IntStream.rangeClosed(1, 9)
                .mapToObj(String::valueOf)
                .collect(Collectors.joining());
        assertThat(ints).isEqualTo("123456789");
    }

    @Test
    public void joiningWithDelimiter() {
        String csv = IntStream.rangeClosed(1, 9)
                .mapToObj(String::valueOf)
                .collect(Collectors.joining(","));
        assertThat(csv).isEqualTo("1,2,3,4,5,6,7,8,9");
    }

    @Test
    public void joiningWithPrefixAndSuffix() {
        String csv = IntStream.rangeClosed(1, 9)
                .mapToObj(String::valueOf)
                .collect(Collectors.joining(",", "[", "]"));
        assertThat(csv).isEqualTo("[1,2,3,4,5,6,7,8,9]");
    }

    @Test
    public void mapping() {
        // Collectors.mapping
        List<String> upperTextMapping = Stream.of("aaa", "bbb", "ccc")
                .collect(Collectors.mapping(String::toUpperCase,
                        Collectors.toList()));
        assertThat(upperTextMapping).containsSequence("AAA", "BBB", "CCC");

        // map()
        List<String> upperText = Stream.of("aaa", "bbb", "ccc")
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        assertThat(upperText).containsSequence("AAA", "BBB", "CCC");
    }

    @Test
    public void reducingWithIdentity() {
        // Collectors.reducing(T identity, BinaryOperator<T> op)
        int sumReducing = IntStream.rangeClosed(1, 10)
                .boxed()
                .collect(Collectors.reducing(1, (sum, i) -> sum + i));
        assertThat(sumReducing).isEqualTo(56);

        String text = Stream.of("aaa", "bbb", "ccc")
                .collect(Collectors.reducing("#", (a, b) -> a + b));
        assertThat(text).isEqualTo("#aaabbbccc");
    }

    @Test
    public void reducing() {
        // Collectors.reducing(BinaryOperator<T> op)
        Optional<String> text = Stream.of("aaa", "bbb", "ccc")
                .collect(Collectors.reducing((a, b) -> a + b));
        assertThat(text.get()).isEqualTo("aaabbbccc");
    }

    @Test
    public void reducingWithMapper() {
        // Collectors.reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)
        int sumLength = Stream.of("a", "bb", "ccc", "dd")
                .collect(Collectors.reducing(100,
                        String::length,
                        (a, b) -> a + b));

        assertThat(sumLength).isEqualTo(108);
    }

    @Test
    public void groupingBy() {
        // Collectors.groupingBy(Function<? super T,? extends K> classifier)
        Map<Integer, List<String>> lengthMap =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.groupingBy(String::length));
        assertThat(lengthMap).containsOnlyKeys(1, 2, 3);
        assertThat(lengthMap).contains(entry(1, Arrays.asList("a", "d")));
        assertThat(lengthMap).contains(entry(2, Arrays.asList("bb", "ee")));
        assertThat(lengthMap).contains(entry(3, Arrays.asList("ccc", "fff")));
        assertThat(lengthMap).containsOnly(entry(1, Arrays.asList("a", "d")),
                entry(2, Arrays.asList("bb", "ee")),
                entry(3, Arrays.asList("ccc", "fff")));
    }

    @Test
    public void groupingByWithDownstream() {
        // Collectors.groupingBy(Function<? super T,? extends K> classifier,
        //                       Collector<? super T,A,D> downstream)
        Map<Integer, Long> countLength =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.groupingBy(String::length,
                                Collectors.counting()));
        assertThat(countLength).containsOnly(entry(1, 2L), entry(2, 2L), entry(3, 2L));
    }

    @Test
    public void groupingByWithMapFactory() {
        // Collectors.groupingBy(Function<? super T,? extends K> classifier,
        //                       Supplier<M> mapFactory,
        //                       Collector<? super T,A,D> downstream)
        Map<String, Long> stringCount =
                Stream.of("a", "bb", "ccc", "A", "dd", "CCC")
                        .collect(Collectors.groupingBy(String::toUpperCase,
                                TreeMap::new,
                                Collectors.counting()));
        assertThat(stringCount).containsOnlyKeys("A", "BB", "CCC", "DD");
        assertThat(stringCount).containsValues(2L, 1L, 2L, 1L);
    }

    @Test
    public void groupingByConcurrent() {
        // groupingByConcurrent(Function<? super T,? extends K> classifier,
        //                      Collector<? super T,A,D> downstream)
        ConcurrentMap<Integer, List<String>> lengthMap =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.groupingByConcurrent(String::length));
        assertThat(lengthMap).containsOnlyKeys(1, 2, 3);
        assertThat(lengthMap).contains(entry(1, Arrays.asList("a", "d")));
        assertThat(lengthMap).contains(entry(2, Arrays.asList("bb", "ee")));
        assertThat(lengthMap).contains(entry(3, Arrays.asList("ccc", "fff")));
        assertThat(lengthMap).containsOnly(entry(1, Arrays.asList("a", "d")),
                entry(2, Arrays.asList("bb", "ee")),
                entry(3, Arrays.asList("ccc", "fff")));
    }

    @Test
    public void groupingByConcurrentWithDownstream() {
        ConcurrentMap<Integer, Long> countLength =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.groupingByConcurrent(String::length,
                                Collectors.counting()));
        assertThat(countLength).containsOnly(entry(1, 2L), entry(2, 2L), entry(3, 2L));
    }

    @Test
    public void groupingByConcurrentWithMapFactory() {
        ConcurrentMap<String, Long> stringCount =
                Stream.of("a", "bb", "ccc", "A", "dd", "CCC")
                        .collect(Collectors.groupingByConcurrent(String::toUpperCase,
                                ConcurrentSkipListMap::new,
                                Collectors.counting()));
        assertThat(stringCount).containsOnlyKeys("A", "BB", "CCC", "DD");
        assertThat(stringCount).containsValues(2L, 1L, 2L, 1L);
    }

    @Test
    public void partitioningBy() {
        Map<Boolean, List<String>> length3 =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.partitioningBy(s -> s.length() > 2));
        assertThat(length3).contains(entry(true, Arrays.asList("ccc", "fff")));
        assertThat(length3).contains(entry(false, Arrays.asList("a", "bb", "d", "ee")));
    }

    @Test
    public void partitioningByWithDownstream() {
        Map<Boolean, Long> length3 =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.partitioningBy(s -> s.length() > 2,
                                Collectors.counting()));
        assertThat(length3).contains(entry(true, 2L));
        assertThat(length3).contains(entry(false, 4L));
    }

    @Test
    public void collectingAndThen() {
        List<String> list = Stream.of("a", "b", "c", "d", "e")
                .map(String::toUpperCase)
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
        assertThat(list).containsOnly("A", "B", "C", "D", "E");
    }

    @Test
    public void toCollection() {
        List<Integer> list = IntStream.rangeClosed(1, 5)
                .boxed()
                .collect(Collectors.toCollection(LinkedList::new));
        assertThat(list).containsOnly(1, 2, 3, 4, 5);
    }
    
    @Test
    public void toList() {
        // int
        List<Integer> list = IntStream.rangeClosed(1, 5)
                .map(i -> i + 100)
                .boxed()
                .collect(Collectors.toList());
        assertThat(list).containsSequence(101, 102, 103, 104, 105);

        // String
        List<String> text = Stream.of("aaa", "bbb", "ccc")
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        assertThat(text).containsSequence("AAA", "BBB", "CCC");
    }

    @Test
    public void toMap() {
        // Collectors.toMap(Function<? super T,? extends K> keyMapper,
        //                  Function<? super T,? extends U> valueMapper)
        Map<String, String> upper =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.toMap(s -> s,
                                String::toUpperCase));
        assertThat(upper).contains(entry("a", "A"));
        assertThat(upper).contains(entry("bb", "BB"));
        assertThat(upper).contains(entry("ccc", "CCC"));
        assertThat(upper).contains(entry("d", "D"));
        assertThat(upper).contains(entry("ee", "EE"));
        assertThat(upper).contains(entry("fff", "FFF"));
    }

    @Test
    public void toMapWithMerge() {
        // Collectors.toMap(Function<? super T,? extends K> keyMapper,
        //                  Function<? super T,? extends U> valueMapper,
        //                  BinaryOperator<U> mergeFunction)
        Map<Integer, String> upper =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.toMap(String::length,
                                s -> s,
                                (s1, s2) -> s1 + "," + s2));
        assertThat(upper).contains(entry(1, "a,d"));
        assertThat(upper).contains(entry(2, "bb,ee"));
        assertThat(upper).contains(entry(3, "ccc,fff"));
    }

    @Test
    public void toMapWithMapSupplier() {
        // Collectors.toMap(Function<? super T,? extends K> keyMapper,
        //                  Function<? super T,? extends U> valueMapper,
        //                  BinaryOperator<U> mergeFunction,
        //                  Supplier<M> mapSupplier)
        Map<Integer, String> upper =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.toMap(String::length,
                                s -> s,
                                (s1, s2) -> s1 + "," + s2,
                                HashMap::new));
        assertThat(upper).contains(entry(1, "a,d"));
        assertThat(upper).contains(entry(2, "bb,ee"));
        assertThat(upper).contains(entry(3, "ccc,fff"));
    }

    @Test
    public void toConcurrentMap() {
        // Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper,
        //                            Function<? super T,? extends U> valueMapper)
        ConcurrentMap<String, String> upper =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.toConcurrentMap(s -> s,
                                String::toUpperCase));
        assertThat(upper).contains(entry("a", "A"));
        assertThat(upper).contains(entry("bb", "BB"));
        assertThat(upper).contains(entry("ccc", "CCC"));
        assertThat(upper).contains(entry("d", "D"));
        assertThat(upper).contains(entry("ee", "EE"));
        assertThat(upper).contains(entry("fff", "FFF"));
    }

    @Test
    public void toConcurrentMapWithMerge() {
        // Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper,
        //                            Function<? super T,? extends U> valueMapper,
        //                            BinaryOperator<U> mergeFunction)
        ConcurrentMap<Integer, String> upper =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.toConcurrentMap(String::length,
                                s -> s,
                                (s1, s2) -> s1 + "," + s2));
        assertThat(upper).contains(entry(1, "a,d"));
        assertThat(upper).contains(entry(2, "bb,ee"));
        assertThat(upper).contains(entry(3, "ccc,fff"));
    }

    @Test
    public void toConcurrentMapWithMapSupplier() {
        // Collectors.toConcurrentMap(Function<? super T,? extends K> keyMapper,
        //                            Function<? super T,? extends U> valueMapper,
        //                            BinaryOperator<U> mergeFunction,
        //                            Supplier<M> mapSupplier)
        ConcurrentMap<Integer, String> upper =
                Stream.of("a", "bb", "ccc", "d", "ee", "fff")
                        .collect(Collectors.toConcurrentMap(String::length,
                                s -> s,
                                (s1, s2) -> s1 + "," + s2,
                                ConcurrentHashMap::new));
        assertThat(upper).contains(entry(1, "a,d"));
        assertThat(upper).contains(entry(2, "bb,ee"));
        assertThat(upper).contains(entry(3, "ccc,fff"));
    }

    @Test
    public void toSet() {
        Set<String> set = Stream.of("a", "b", "c", "b", "d")
                .collect(Collectors.toSet());
        assertThat(set).containsOnly("a", "b", "c", "d");
    }
}

一応ソースはあげときました。

github.com

終わり。