Stream 的 reduce 與 collect

```List<Person> persons = ...; int sum = 0; for(Person person : persons) {     if(person.getGender() == Person.Gender.MALE) {         sum += person.getAge();     } } int average = sum / persons.size();```

```int max = 0; for(Person person : persons) {     if(person.getGender() == Person.Gender.MALE) {         if(person.getAge() > max) {             max = person.getAge();         }     } }```

```int sum = persons.stream()                  .filter(person -> person.getGender() == Person.Gender.MALE)                  .mapToInt(Person::getAge)                  .sum();              int average = (int) persons.stream()                            .filter(person -> person.getGender() == Person.Gender.MALE)                            .mapToInt(Person::getAge)                            .average()                            .getAsDouble();              int max = persons.stream()                  .filter(person -> person.getGender() == Person.Gender.MALE)                  .mapToInt(Person::getAge)                  .max()                  .getAsInt();```

JDK8的`IntStream`提供了`sum()``average()``max()``min()`等方法，那麼如果有其它的計算需求呢？觀察先前的迴圈結構，實際上都是將一組數據逐步取出削減，然而透過指定運算以取得結果的結構，JDK8將這個流程結構通用化，定義了`reduce()`方法來達到自訂運算需求。例如，以上三個流程，也可以使用`reduce()`重新撰寫如下：

```int sum = persons.stream()                  .filter(person -> person.getGender() == Person.Gender.MALE)                  .mapToInt(Person::getAge)                  .reduce((total, age) ->  total + age)                  .getAsInt(); ```

```long males = persons.stream()                 .filter(person -> person.getGender() == Person.Gender.MALE)                 .count(); ```

``` int average = persons.stream()                      .filter(person -> person.getGender() == Person.Gender.MALE)                      .mapToInt(Person::getAge)                      .reduce((total, age) ->  total + age)                      .getAsInt() / males;             int max = persons.stream()                  .filter(person -> person.getGender() == Person.Gender.MALE)                  .mapToInt(Person::getAge)                  .reduce(0, (currentMax, age) -> age > currentMax ? age : currentMax);```

`reduce()`的Lambda表示式，必須接受兩個引數，第一個引數為走訪該組數據上一元素後的運算結果，第二個引數為目前走訪元素，Lambda表示式本體就是你原先在迴圈中打算進行的運算；`reduce()`如果如上例中首兩個程式片段沒有指定初值，就會試著使用該組數據中第一個元素，作為第一次呼叫Lambda表示式時的第一個引數值，因為考量到數據組可能為空，因此`reduce()`不指定初值的版本，會傳回`OptionalInt`（非基本型態數據組，則會是`Optional`）。

```List<Person> males = persons.stream()                             .filter(person -> person.getGender() == Person.Gender.MALE)                             .collect(toList()); // toList() 是 java.util.stream.Collectors 的靜態方法 ```

`Collectors``toList()`方法傳回的並不是`List`，而是`java.util.stream.Collector`實例，`Collector`主要的四個方法是：

• `suppiler()`：傳回`Suppiler`，定義收集結果的新容器如何建立
• `accumulator()`：傳回`BiConsumer`，定義如何使用結果容器收集物件
• `combiner()`：傳回`BinaryOperator`，定義若有兩個結果容器時，如何合併為一個結果容器
• `finisher()`：傳回`Function`，選擇性地定義如何將結果轉換為最後的結果容器

`List<Person> males = persons.stream()`
`                            .filter(person -> person.getGender() == Person.Gender.MALE)`
`                            .collect(`
`                                 () -> new ArrayList<>(),`
`                                 (maleLt, person) -> maleLt.add(person),`
`                                 (maleLt1, maleLt2) -> maleLt1.addAll(maleLt2)`
`                            );`

`collect()`需要收集物件時，會使用第一個Lambda來取得容器物件，這相當於`Collector``suppiler()`之作用，第二個Lambda定義了如何收集物件，也就是`Collector``accumulator()`之作用，在使用具有平行處理能力的`Stream`時，有可能會使用多個容器對原數據組進行分而治之（Divide and conquer），當每個小任務完成時，該如何合併，就是第三個Lambda要定義的，喔！別忘了可以用方法參考，因此上面可以寫成以下比較簡潔：

`List<Person> males = persons.stream()`
`                            .filter(person -> person.getGender() == Person.Gender.MALE)`
`                            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);`

```Map<Person.Gender, List<Person>> males = persons.stream()                   .collect(                       groupingBy(Person::getGender));```

```Map<Person.Gender, List<String>> males = persons.stream() ``````                  .collect( ````                  ````    groupingBy(Person::getGender,                       mapping(Person::getName, ````                  toList())));`

```Map<Person.Gender, Integer> males = persons.stream()                   .collect(                           groupingBy(Person::getGender,                                reducing(0, Person::getAge, Integer::sum))                   );```

`Map<Person.Gender, Double> males = persons.stream()`
`                  .collect(`
`                          groupingBy(Person::getGender, `
`                               averagingInt(Person::getAge))`
`                  );`