カスタム 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.0Collectorインターフェースの直接実装
より細かく制御するには、クラスとして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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- groupingBy:要素の分類
- partitioningBy と counting
- toMap、joining、summarizing
- カスタム Collector の作成