54
54

More than 5 years have passed since last update.

java.util.stream.Collectorの活用法

Last updated at Posted at 2017-01-23
1 / 30

目次

  • はじめに
  • 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は使い方次第で好きなオブジェクトに変換できる

Enjoy collecting!!


参考

54
54
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
54