0Pricing
Java Academy · レッスン

カスタム Collector の作成

Collector インターフェースを実装し、ドメイン固有の用途で再利用できる集約処理を作成します。

「カスタム Collector の作成」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

カスタムコレクターを作成する理由

組み込みコレクターでは要件を満たせない場合は、Collector<T, A, R>インターフェースを実装して、再利用可能で組み合わせ可能な集計ロジックを作成します。Tは入力、Aは可変のアキュムレーター、Rは結果を表します。

Collectorインターフェース

Collectorには5つの要素があります:

  • supplier() — 初期アキュムレーターを作成します
  • accumulator() — 1つの要素をアキュムレーターに取り込みます
  • combiner() — 2つのアキュムレーターを統合します(並列ストリーム用)
  • finisher() — アキュムレーターを最終結果に変換します
  • characteristics() — 最適化のヒントを提供します

Collector.of()ファクトリ

カスタムコレクターを作成する最も簡単な方法は、インターフェースを直接実装せずにCollector.of()を使用することです:

import java.util.stream.*;

// Collector that builds a comma-separated String
Collector<String, StringBuilder, String> csvCollector = Collector.of(
    StringBuilder::new,                     // supplier
    (sb, s) -> {
        if (sb.length() > 0) sb.append(',');
        sb.append(s);
    },                                       // accumulator
    (a, b) -> {
        if (a.length() > 0 && b.length() > 0) a.append(',');
        a.append(b); return a;
    },                                       // combiner
    StringBuilder::toString                  // finisher
);

String csv = Stream.of("Alice","Bob","Carol").collect(csvCollector);
System.out.println(csv); // Alice,Bob,Carol

カスタムコレクター: TopN

Comparatorに基づいて上位N個の要素だけを保持するコレクターを作成します:

import java.util.*;
import java.util.stream.*;

static <T> Collector<T, ?, List<T>> topN(int n, Comparator<T> comp) {
    return Collector.of(
        () -> new ArrayList<T>(),
        (list, e) -> {
            list.add(e);
            list.sort(comp.reversed());
            if (list.size() > n) list.remove(list.size() - 1);
        },
        (a, b) -> {
            a.addAll(b);
            a.sort(comp.reversed());
            while (a.size() > n) a.remove(a.size() - 1);
            return a;
        },
        Collector.Characteristics.UNORDERED
    );
}

TopNコレクターの使用

ストリームにカスタムTopNコレクターを適用します:

List<Integer> top3 = Stream.of(5,2,9,1,7,3,8)
    .collect(topN(3, Comparator.naturalOrder()));
System.out.println(top3); // [9, 8, 7]

Characteristicsフラグ

3つのオプションのCharacteristicsで、ストリームの最適化を調整できます:

  • IDENTITY_FINISH — finisherが恒等関数であるため、省略できます
  • UNORDERED — 結果の順序が重要ではありません
  • CONCURRENT — アキュムレーターを並行して呼び出せます

誤ったフラグを設定すると(たとえば、スレッドセーフでないのにCONCURRENTを設定すると)、バグの原因になります。

カスタムコレクター: 移動平均

2回目の走査を行わずに、移動平均を保持します:

record RunningAvg(long count, double sum) {
    RunningAvg add(double v) { return new RunningAvg(count+1, sum+v); }
    RunningAvg merge(RunningAvg o) { return new RunningAvg(count+o.count, sum+o.sum); }
    double avg() { return count == 0 ? 0 : sum / count; }
}

Collector<Double, RunningAvg[], Double> avgCollector = Collector.of(
    () -> new RunningAvg[]{new RunningAvg(0, 0)},
    (a, v) -> a[0] = a[0].add(v),
    (a, b) -> { a[0] = a[0].merge(b[0]); return a; },
    a -> a[0].avg()
);

double avg = Stream.of(10.0, 20.0, 30.0).collect(avgCollector);
System.out.println(avg); // 20.0

Collectorインターフェースの直接実装

より細かく制御するには、クラスとしてCollector<T,A,R>を実装します:

import java.util.function.*;
import java.util.stream.*;

class StringJoiningCollector implements Collector<String, List<String>, String> {
    private final String delimiter;
    StringJoiningCollector(String delimiter) { this.delimiter = delimiter; }
    public Supplier<List<String>> supplier() { return ArrayList::new; }
    public BiConsumer<List<String>, String> accumulator() { return List::add; }
    public BinaryOperator<List<String>> combiner() {
        return (a,b) -> { a.addAll(b); return a; };
    }
    public Function<List<String>, String> finisher() {
        return list -> String.join(delimiter, list);
    }
    public Set<Characteristics> characteristics() { return Set.of(); }
}

カスタムコレクターと組み込みコレクターの比較

カスタムコレクターを作成する前に、組み込みコレクターの組み合わせで同じ結果を実現できるか確認してください。組み込みコレクターはすでにテストされ、最適化されています:

// Instead of custom, compose:
Map<String, IntSummaryStatistics> stats = orders.stream().collect(
    Collectors.groupingBy(Order::status,
        Collectors.summarizingInt(Order::amount))
);
// This replaces many custom collector implementations

カスタムコレクターのスレッドセーフティ

CONCURRENT特性を設定すると、アキュムレーターが複数のスレッドから同時に呼び出されます。そのため、アキュムレーターはスレッドセーフでなければなりません(ConcurrentHashMapやAtomicLongなどを使用します)。CONCURRENTを設定しない場合は、combinerが並列処理を安全に処理します。

実践例: ヒストグラムコレクター

数値を区間に分けてヒストグラムMapに格納します:

Collector<Integer, Map<Integer, Long>, Map<Integer, Long>> histogram =
    Collector.of(
        LinkedHashMap::new,
        (map, val) -> map.merge(val / 10 * 10, 1L, Long::sum),
        (a, b) -> { b.forEach((k,v) -> a.merge(k,v,Long::sum)); return a; },
        Collector.Characteristics.IDENTITY_FINISH
    );

Map<Integer,Long> hist = Stream.of(5,15,25,12,8,22,18,3)
    .collect(histogram);
hist.forEach((bucket, cnt) -> System.out.println(bucket+"-"+(bucket+9)+": "+cnt));

理解度チェック

finisher関数が恒等変換であり、省略できることを示すCollector.Characteristicsフラグはどれですか?

復習: カスタムコレクター

重要なポイント:

  • Collector<T,A,R>: Tは入力、Aはアキュムレーター、Rは結果を表します
  • クラスを作成せずに簡単に実装するには、Collector.of()を使用します
  • supplier/accumulator/combiner/finisher/characteristicsの5つの要素で構成されます
  • IDENTITY_FINISH、UNORDERED、CONCURRENTは最適化フラグです
  • 組み込みコレクターの組み合わせを優先し、必要な場合だけカスタム実装を使用します

よくある質問

「カスタム Collector の作成」レッスンは無料ですか?

はい。「カスタム Collector の作成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「カスタム Collector の作成」で何を学びますか?

Collector インターフェースを実装し、ドメイン固有の用途で再利用できる集約処理を作成します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「カスタム Collector の作成」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. groupingBy:要素の分類
  2. partitioningBy と counting
  3. toMap、joining、summarizing
  4. カスタム Collector の作成
← Java Academyに戻る