目次
- はじめに
- Collectorを作る
- Collectorを楽に使う
- Collectorをまとめる
- Collectorから生成する
はじめに
- Java 8以降Stream APIはfor文に代わり広く使われる
- Streamには終端処理がセットになり、その代表がcollectメソッド
- collectメソッドの引数が
java.util.stream.Collector
Collectorを作る
// 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を楽に使う
// サンプルメソッド
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はもう少し汎用的なメソッド
ところで
class Hoge {
String methodA(String str) {
if (str == null || str.equals("")) {
return "A";
}
// ..
}
String methodB(String str) {
if (str == null || str.equals("")) {
return "B";
}
// ..
}
}
こんなコードをどうする?
共通化する(定義)
class StringUtil {
boolean isEmpty(String str) {
return str == null || str.equals("");
}
}
共通化する(呼び出し)
class Hoge {
String methodA(String str) {
if (isEmpty(str)) {
return "A";
}
// ..
}
String methodB(String str) {
if (isEmpty(str)) {
return "B";
}
// ..
}
}
じゃあこれは?
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を共通化する
// 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変換)
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()がシンプルかつ汎用的になる
よく見かける処理
// イベント履歴を持つクラス
@AllArgsConstructor
class History {
List<String> events;
}
// イベントのリストにする
List<String> events = stream().collect(toList());
// Historyを生成
History history = new History(events);
直接オブジェクトを生成できる
List<String> events = stream().collect(toList());
History history = new History(events);
// ↓
// 直接Historyに変換
History history = stream()
.collect(collectingAndThen(toList(), History::new));
collectはListやMapを生成するためだけのメソッドではない
更に組み合わせる
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変換)
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));
活用例(その他)
// Jsonに変換する
stream
.collect(toJson());
// CSVに変換する
stream
.collect(toCsv());
// リクエストパラメータに変換する
stream
.collect(toRequestParam());
// SQLに変換する
stream
.collect(toSql());
Collectorは汎用的なCollector同士を組み合わせて更に再利用できる
より実践的な使い方は
gakuzzzzさんのCollectorの活用法
Eclipse Collectionsのユーティリティクラス(ソースも参考になる)
まとめ
- Java 8以降Streamは頻出するAPI
- StreamがいるところにCollectorはつきもの
- collectメソッドは汎用的
- Collectorは組み合わせて共通化できる
- Collectorは使い方次第で好きなオブジェクトに変換できる