0Pricing
Java Academy · Lezione

Creazione di un Collector personalizzato

Implementi l'interfaccia Collector per creare un'aggregazione riutilizzabile per un caso d'uso specifico del dominio.

Creazione di un Collector personalizzato è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.

Perché creare un collector personalizzato?

Quando i collector integrati non sono adatti al dominio, implementi l'interfaccia Collector<T, A, R> per creare una logica di aggregazione riutilizzabile e componibile. T=input, A=accumulatore mutabile, R=risultato.

L'interfaccia Collector

Un Collector è composto da quattro componenti:

  • supplier(): crea l'accumulatore iniziale
  • accumulator(): incorpora un elemento nell'accumulatore
  • combiner(): unisce due accumulatori, per gli stream paralleli
  • finisher(): trasforma l'accumulatore nel risultato finale
  • characteristics(): fornisce suggerimenti per l'ottimizzazione

Factory Collector.of()

Il modo più semplice per creare un collector personalizzato consiste nell'utilizzare Collector.of() senza implementare direttamente l'interfaccia:

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

Collector personalizzato: TopN

Crei un collector che conservi solo i primi N elementi in base a un comparator:

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
    );
}

Utilizzo del collector TopN

Applichi il collector personalizzato TopN a uno stream:

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

Flag delle caratteristiche

Tre caratteristiche facoltative consentono di ottimizzare gli stream:

  • IDENTITY_FINISH: il finisher è l'identità; lo si può omettere
  • UNORDERED: l'ordine del risultato non è importante
  • CONCURRENT: l'accumulatore può essere chiamato contemporaneamente

L'impostazione del flag errato, ad esempio CONCURRENT senza sicurezza tra thread, causa bug.

Collector personalizzato: media progressiva

Mantenga una media progressiva senza una seconda passata:

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

Implementazione diretta dell'interfaccia Collector

Per un controllo maggiore, implementi Collector<T,A,R> come classe:

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(); }
}

Confronto tra collector personalizzati e integrati

Prima di scrivere un collector personalizzato, verifichi se la composizione di collector integrati consente di ottenere lo stesso risultato: sono già testati e ottimizzati:

// 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

Sicurezza tra thread nei collector personalizzati

Se è impostata la caratteristica CONCURRENT, l'accumulatore viene chiamato contemporaneamente da più thread. L'accumulatore deve quindi essere sicuro tra thread, ad esempio utilizzando ConcurrentHashMap o AtomicLong. Senza CONCURRENT, il combiner gestisce il parallelismo in modo sicuro.

Caso d'uso reale: collector per istogrammi

Raggruppi i valori numerici in intervalli all'interno di una map per creare un istogramma:

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));

Verifica rapida

Quale flag di Collector.Characteristics indica che la funzione finisher è la trasformazione identità e può essere omessa?

Riepilogo: collector personalizzati

Punti chiave:

  • Collector<T,A,R>: T=input, A=accumulatore, R=risultato
  • Utilizzi Collector.of() per un'implementazione rapida senza creare una classe
  • supplier/accumulator/combiner/finisher/characteristics sono le 5 parti
  • IDENTITY_FINISH, UNORDERED, CONCURRENT sono flag di ottimizzazione
  • Preferisca la composizione di collector integrati; ricorra a quelli personalizzati solo quando necessario

Domande Frequenti

La lezione «Creazione di un Collector personalizzato» è gratuita?

Sì — il testo completo di «Creazione di un Collector personalizzato» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.

Cosa imparerò in «Creazione di un Collector personalizzato»?

Implementi l'interfaccia Collector per creare un'aggregazione riutilizzabile per un caso d'uso specifico del dominio. Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Java Academy?

Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Creazione di un Collector personalizzato»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Java Academy?

Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. groupingBy: classificare gli elementi
  2. partitioningBy e counting
  3. toMap, joining e summarizing
  4. Creazione di un Collector personalizzato
← Torna a Java Academy