0Pricing
Java Academy · Lekcja

Tworzenie własnego kolektora

Implementuj interfejs Collector, aby utworzyć wielokrotnego użytku agregację dla konkretnego przypadku użycia w danej domenie.

Tworzenie własnego kolektora to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Po co tworzyć własny kolektor?

Gdy wbudowane kolektory nie pasują do Państwa domeny, należy zaimplementować interfejs Collector<T, A, R>, aby utworzyć wielokrotnie używaną i kompozycyjną logikę agregowania. T=dane wejściowe, A=zmienny akumulator, R=wynik.

Interfejs Collector

Collector składa się z pięciu elementów:

  • supplier() — tworzy początkowy akumulator
  • accumulator() — dodaje jeden element do akumulatora
  • combiner() — scala dwa akumulatory, na przykład w przypadku strumieni równoległych
  • finisher() — przekształca akumulator w końcowy wynik
  • characteristics() — dostarcza wskazówek optymalizacyjnych

Fabryka Collector.of()

Najprostszym sposobem utworzenia własnego kolektora jest użycie Collector.of() bez bezpośredniego implementowania interfejsu:

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

Własny kolektor: TopN

Utwórz kolektor, który zachowuje tylko N najlepszych elementów według komparatora:

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

Używanie kolektora TopN

Zastosuj własny kolektor TopN do strumienia:

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

Flagi charakterystyk

Trzy opcjonalne charakterystyki służą do dostrajania optymalizacji strumienia:

  • IDENTITY_FINISH — finisher jest funkcją tożsamościową, więc można go pominąć
  • UNORDERED — kolejność wyniku nie ma znaczenia
  • CONCURRENT — akumulator może być wywoływany współbieżnie

Ustawienie niewłaściwej flagi, na przykład CONCURRENT bez bezpieczeństwa wątkowego, powoduje błędy.

Własny kolektor: średnia krocząca

Utrzymuj średnią kroczącą bez wykonywania drugiego przejścia:

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

Bezpośrednia implementacja interfejsu Collector

Aby uzyskać większą kontrolę, zaimplementuj Collector<T,A,R> jako klasę:

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

Porównanie własnego i wbudowanego kolektora

Przed napisaniem własnego kolektora sprawdź, czy połączenie wbudowanych kolektorów pozwala uzyskać ten sam rezultat — są one już przetestowane i zoptymalizowane:

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

Bezpieczeństwo wątkowe własnych kolektorów

Jeśli ustawiono charakterystykę CONCURRENT, akumulator jest wywoływany jednocześnie przez wiele wątków. Musi więc być bezpieczny wątkowo, na przykład należy użyć ConcurrentHashMap lub AtomicLong. Bez CONCURRENT funkcja combiner bezpiecznie obsługuje przetwarzanie równoległe.

Praktyczny przypadek: kolektor histogramu

Przypisz wartości liczbowe do przedziałów i zapisz je w mapie histogramu:

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

Szybkie sprawdzenie

Która flaga Collector.Characteristics wskazuje, że funkcja finisher jest transformacją tożsamościową i można ją pominąć?

Podsumowanie: własne kolektory

Najważniejsze informacje:

  • Collector<T,A,R>: T=dane wejściowe, A=akumulator, R=wynik
  • Użyj Collector.of(), aby szybko utworzyć implementację bez klasy
  • supplier/accumulator/combiner/finisher/characteristics to pięć elementów
  • IDENTITY_FINISH, UNORDERED i CONCURRENT to flagi optymalizacyjne
  • Preferuj łączenie wbudowanych kolektorów; własny twórz tylko wtedy, gdy jest potrzebny

Często zadawane pytania

Czy lekcja „Tworzenie własnego kolektora” jest bezpłatna?

Tak — pełny tekst „Tworzenie własnego kolektora” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Tworzenie własnego kolektora”?

Implementuj interfejs Collector, aby utworzyć wielokrotnego użytku agregację dla konkretnego przypadku użycia w danej domenie. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Tworzenie własnego kolektora”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. groupingBy: klasyfikowanie elementów
  2. partitioningBy i counting
  3. toMap, joining i summarizing
  4. Tworzenie własnego kolektora
← Powrót do Java Academy