【Java Gold🥇】Collectorのつかいかた（Java SE 11 -）

`java.util.stream.Collector`を使うと、配列のすべての要素どうしを計算する処理（数列の総和計算など）を効率化できるようだ。

# 総和計算を効率化したい
Q. 数が$+$で連結されている式$1+2+ \cdots +10$を効率よく（計算ステップ数が少なくなるように）3人で分担して計算するにはどのようにすればいいですか？
A. 以下の表のように『並列処理による効率化』をして計算すると4ステップで計算できる。

|計算者＼ステップ数|1|2|3|4|
|:---:|:---:|:---:|:---:|:---:|
|A|$a_1=1+2$|$a_2=a_1+3$|$a_3=a_2+b_2$|$\sigma=a_3+c_3$|
|B|$b_1=4+5$|$b_2=b_1+6$|-|-|
|C|$c_1=7+8$|$c_2=c_1+9$|$c_3=c_2+10$|-|

この効率化は、与えられた式
$\sigma = 1+2+3+4+5+6+7+8+9+10$
を
$\sigma = (((1+2)+3)+((4+5)+6))+(((7+8)+9)+10)$
のように、式を<B>いくつかのまとまりに分けて計算した</B>ことを意味する。

# 一般的な処理に応用する
$+$以外の演算子で連結されている式に対しても同じ『並列処理による効率化』が使えるだろうか？
ここで一般化した演算$\circ$に対して考えてみよう。

演算$\circ$における、総和計算に代わる計算（処理）は
$X = a_1 \circ a_2 \circ  \cdots \circ a_n$
といった表現になる。

先ほどの効率化方法を行うためには<B>演算をどのようなまとまりに分割しても全体の結果は変わってはいけない</B>。　したがって<B>演算$\circ$が『並列処理による効率化』可能である条件は結合法則 $(a \circ b) \circ c = a \circ (b \circ c)$ を満たすこと</B>といえる。

結合法則を満たしさえすればどんな演算でも演算子で連結された式の計算は『並列処理による効率化』ができる。

# 総和の式をCollectorを使って計算する
`Stream`と`Collector`を組み合わせて使うと『並列処理による効率化』が簡単に実行できる。
`Collector`インスタンスを作成するには、以下の表にある３つの型を決め、それに応じた４つの処理を定義する必要がある。

__使用する３つの型__
|ジェネリクス|内容|総和計算でいうと…|
|:---:|:---:|:---:|
|`T`|処理の入力要素の型|$1,2,\cdots$といった数値|
|`A`|処理の中間状態を保持するオブジェクトの型|$(1+2)$といった部分的演算結果|
|`R`|処理結果の型|総和計算の結果の数値|

__実装する４つの処理__
|処理名|型|入力|出力|処理内容|総和計算でいうと…|
|:---:|:---:|:---:|:---:|:---:|:---:|
|supplier|`Supplier<A>`|-|`A`|新しい結果コンテナの作成|$()$の枠を作る|
|accumulator|`BiConsumer<A,T>`|`A`, `T`|-|結果コンテナ`A`への新しいデータ要素`T`の組み込み|$(1+2)+3$|
|combiner|`BinaryOperator<A>`|`A`,`A`|`A`|2つの結果コンテナ`A`を1つに結合する|$(1+2)+(3+4)$|
|finisher|`Function<A,R>`|`A`|`R`|結果コンテナ`A`を最終的な出力に変換する|計算結果を出力する|

４つの処理を実装したら`Collector`インスタンスを生成しよう。
`Collector`ファクトリメソッドである`of`メソッドで生成できる。
``` java
Collector<T, A, R> collector = Collector.of(supplier, accumulator, combiner, finisher,	characteristics);
```

`of`メソッドの最後の引数は並行処理に対する設定らしい。
`Characteristics.CONCURRENT`を

$a_1, a_2, \cdots , a_n$の列を`Stream`で与えて、演算処理を`Stream`の`collect`メソッドで実行する。
``` java
Stream<T> stream;
R result = stream.collect(collector);
```

## `Integer`の総和計算の実装
``` java
class Test {
	Integer calcSum(List<Integer> list) {
        // 結果コンテナ
		class IntContainer {
			public int value;

			IntContainer() {
				this(0);
			}

			IntContainer(int value) {
				this.value = value;
			}
		}

        // 新しい結果コンテナの作成
		Supplier<IntContainer> supplier = IntContainer::new;

        // 結果コンテナ`IntContainer`への新しいデータ要素`Integer`の組み込み
		BiConsumer<IntContainer, Integer> accumulator = (container, i) -> container.value += i;

        // 2つの結果コンテナ`IntContainer`を1つに結合する
		BinaryOperator<IntContainer> combiner = (a, b) -> new IntContainer(a.value + b.value);

        // 結果コンテナ`IntContainer`を最終的な出力に変換する
		Function<IntContainer, Integer> finisher = container -> container.value;

		// 並行処理を前提とする
		Characteristics characteristics = Characteristics.CONCURRENT;

        // Collectorを生成
		Collector<Integer, IntContainer, Integer> collector = Collector.of(supplier, accumulator, combiner, finisher,	characteristics);

        // Collectorによる処理を実行する
		Stream<Integer> stream = list.stream().parallel();
		return stream.collect(collector);
	}
}
```

# 実装例：複数の文字列をカンマで連結する
複数の文字列をカンマ区切りで連結してひとつの文字列で出力する処理を考える。
文字の連結も結合法則を満たす処理なので、`Collector`で処理可能である。

文字の連結操作`+`で連結するより`StringBuilder`を使ったほうが処理速度が向上する。
したがって、この処理は『処理の中間状態を保持するオブジェクトの型`A`』に`StringBuilder`を使う。

``` java
class Test {
	String test() {
		// 処理の初めに行う初期化：StringBuilderのインスタンスを生成する
		Supplier<StringBuilder> supplier = () -> new StringBuilder();

		// 次の要素を取り込み処理する：StringをStringBuilderに追加する
		BiConsumer<StringBuilder, String> accumulator = (sb, str) -> {
			if (sb.length() > 0)
				sb.append(", ");
			sb.append(str);
		};

		// まとまりの処理を結合する処理：StringBuilderどうしを連結する
		BinaryOperator<StringBuilder> combiner = (a, b) -> {
			if (a.length() > 0)
				a.append(", ");
			return a.append(b);
		};

		// 中間生成物を結果に変換する処理：StringBuilderで文字列を生成する
		Function<StringBuilder, String> finisher = sb -> {
			return sb.toString();
		};

		// 並行処理を前提とする
		Characteristics characteristics = Characteristics.CONCURRENT;

        // Collectorを生成
		Collector<String, StringBuilder, String> collector = Collector.of(supplier, accumulator, combiner, finisher, characteristics);

        // Collectorによる処理を実行する
    	Stream<String> stream = list.stream().parallel();
		return stream.collect(collector);
	}
}
```