java.util.stream.Collectorの活用法

## 目次
- はじめに
- Collectorを作る
- Collectorを楽に使う
- Collectorをまとめる
- Collectorから生成する

---
## はじめに
- Java 8以降Stream APIはfor文に代わり広く使われる
- Streamには終端処理がセットになり、その代表がcollectメソッド
- collectメソッドの引数が`java.util.stream.Collector`

---
## Collectorを作る
```java
// ArrayListに変換するCollector
Collector<T, ?, List<T>> toArrayList = Collector.of(
        ArrayList::new,
        List::add,
        (l1, l2) -> {
            l1.addAll(l2);
            return l1;
        },
        Characteristics.IDENTITY_FINISH);

// 実行
.stream().collect(toArrayList);
```

詳しい方法は沢山の紹介記事があるのでそちらで

---
### Collectorを使うとStreamの変換処理が行える

---
## Collectorを楽に使う
```java
// サンプルメソッド
Stream<String> stream() {
  return Stream.of("1","20","30");
}
// Listに変換: [1, 20, 30]
stream().collect(toList());
// 文字列連結: "<1,20,30>"
stream().collect(joining(",", "<", ">"));
// グルーピング: {1=[1], 2=[20, 30]}
stream().collect(groupingBy(String::length));
// 変更できないList
stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
```
※ Collectorsはstatic importを使用

---
### Collectorsを使うと楽にStreamの変換が行える

---
### ただし、Collectorを紹介している記事は大体ここまでが多い...

---
# 結果

---
## ユーザの声

> ユーザ A:  Collectorなんてそうそう作らない
> ユーザ B:  毎回Collectors呼ぶのは分かりにくい
> ユーザ C:  他言語みたいにStreamに直接toListとかtoMapとか用意してほしい

それは確かだけど、collectはもう少し汎用的なメソッド

---
## ところで

```java
class Hoge {
	String methodA(String str) {
		if (str == null || str.equals("")) {
			return "A";
		}
		// ..
	}
	String methodB(String str) {
		if (str == null || str.equals("")) {
			return "B";
		}
		// ..
	}
}
```

---
## こんなコードをどうする?

---
## 共通化する(定義)

```java
class StringUtil {
	boolean isEmpty(String str) {
		return str == null || str.equals("");
	}
}
```

---
## 共通化する(呼び出し)

```java
class Hoge {
	String methodA(String str) {
		if (isEmpty(str)) {
			return "A";
		}
		// ..
	}
	String methodB(String str) {
		if (isEmpty(str)) {
			return "B";
		}
		// ..
	}
}
```

---
## じゃあこれは?
```java
String methodA(List<String> list) {
  list.stream()
    .map(/* ... */)
    .collect(collectingAndThen(toList(),Collections::unmodifiableList));
}
 
String methodB(List<String> list) {
  list.stream()
    .filter(/* ... */)
    .collect(collectingAndThen(toList(),Collections::unmodifiableList));
}
```

---
## Collectorを共通化する
```java
// Collectorのユーティリティクラス
class Collectors2 {
　　　　// UnmodifiableなListに変換するCollectorを返す
  static <T> Collector<T,?,List<T>> toUnmodifiableList() {
    return collectingAndThen(toList(), Collections::unmodifiableList);
  }
}
// ユーティリティを使用
stream()
　　　　.collect(collectingAndThen(toList(),Collections::unmodifiableList));
// ↓
stream()
　　　　.collect(toUnmodifiableList());
```

---
## メソッドなので名前は自由

- toUnmodifiableList()
- toImmutableList()
- il()

とは言えわかり易さに配慮は必要

---
### 活用例(Stream変換)
```java
class Collectors2 {
  // グルーピングしてMap.EntityのStreamを返すCollector
  static <T, K> Collector<T, ?, Stream<Map.Entry<K, List<T>>>> grouping(Function<T, K> mapper) {
    return collectingAndThen(
　　　　　　　　　　　　　　　　groupingBy(mapper),
　　　　　　　　　　　　　　　　m -> m.entrySet().stream()
　　　　　　　　);
  }
}
//　グルーピングして、再度Streamに変換
stream()
    .collect(groupingBy(String::length)).entrySet().stream();
// ↓
stream()
    .collect(grouping(String::length));

```

--- 
### StringのようにCollectorもユーティリティを用意することで、collect()がシンプルかつ汎用的になる

---
### よく見かける処理
```java
// イベント履歴を持つクラス
@AllArgsConstructor
class History {
    List<String> events;
}

// イベントのリストにする
List<String> events = stream().collect(toList());
// Historyを生成
History history = new History(events);
```

---
## 直接オブジェクトを生成できる
```java
List<String> events = stream().collect(toList());
History history = new History(events);

// ↓

// 直接Historyに変換
History history = stream()
    .collect(collectingAndThen(toList(),　History::new));
```

---
## collectはListやMapを生成するためだけのメソッドではない

---
## 更に組み合わせる
```java
class Collectors2 {
  // Listに変換後、mapper関数を実行するCollectorを返す
　　　　static <T, R> Collector<T, ?, R> toListAnd(Function<List<T>, R> mapper) {
    return collectingAndThen(toList(), mapper);
　　　　}
}

//　呼び出し
History history = stream()
  .collect(collectingAndThen(toList(),　History::new));
//　↓
History history = stream()
  .collect(toListAnd(History::new));
```

---
## 活用例(Optional変換)
```java
class Collectors2 {
  // toList()が空ならOptional.empty()、空でなければOptional<List<T>>を返す
  static <T> Collector<T, ?, Optional<List<T>>> toOptionalList() {
    return collectingAndThen(toList(), l -> l.isEmpty() ? Optional.empty() : Optional.of(l));
  }
  // Listが空でない時だけ、そのリストを引数に関数を実行
  static <T, R> Collector<T, ?, Optional<R>> ifNotEmptyList(Function<List<T>, R> mapper) {
    return collectingAndThen(toOptionalList(), op -> op.map(mapper));
  }
}

// Listが空でない時だけ、Historyを生成
Optional<History> history = stream()
    .collect(ifNotEmptyList(History::new));
```

---
## 活用例(その他)
```java
// Jsonに変換する
stream
    .collect(toJson());
// CSVに変換する
stream
    .collect(toCsv());
// リクエストパラメータに変換する
stream
    .collect(toRequestParam());
// SQLに変換する
stream
    .collect(toSql());
```

---
## Collectorは汎用的なCollector同士を組み合わせて更に再利用できる

---
## より実践的な使い方は
#### gakuzzzzさんのCollectorの活用法
- [Java8 Stream API の Collector を合成する](https://gist.github.com/gakuzzzz/a8b68317e3fcbb258291ef50c377efba)
- [とあるDoma2の使い方](http://gakuzzzz.github.io/slides/doma_practice/#21)

#### Eclipse Collectionsのユーティリティクラス(ソースも参考になる)

- [Collectors2](https://www.eclipse.org/collections/javadoc/8.0.0/org/eclipse/collections/impl/collector/Collectors2.html)

---
## まとめ
- Java 8以降Streamは頻出するAPI
- StreamがいるところにCollectorはつきもの
- collectメソッドは汎用的
- Collectorは組み合わせて共通化できる
- Collectorは使い方次第で好きなオブジェクトに変換できる

---
## Enjoy collecting!!


---
## 参考
http://www.ne.jp/asahi/hishidama/home/tech/java/collector.html