CodeGym /Kurslar /JAVA 25 SELF /Xüsusi Collector və Spliterator

Xüsusi Collector və Spliterator

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Xüsusi Collector-lar: nə vaxt və necə yazmalı

Java Stream API-də axını kolleksiyaya və ya aqreqata çevirmək üçün Collector interfeysindən istifadə olunur. Adətən siz Collectors sinfindəki hazır kollektorlardan (toList(), toMap(), groupingBy() və s.) istifadə edirsiniz, lakin bəzən xüsusi bir şey lazım olur — bu halda öz Collector-unuzu yaza bilərsiniz.

Collector — axının elementlərindən yekun nəticənin necə yığılacağını təsvir edən obyektidir. O, dörd (əslində beş) əsas komponenti müəyyənləşdirir:

  • supplier — elementləri toplamaq üçün yeni konteyner yaradır (məsələn, yeni siyahı və ya xəritə).
  • accumulator — növbəti elementi konteynerə əlavə edir.
  • combiner — iki konteyneri birləşdirir (paralel stream-lər üçün vacibdir!).
  • finisher — konteyneri yekun nəticəyə çevirir (məsələn, onu dəyişməz edir və ya başqa tipə çevirir).
  • characteristics — kollektorun xüsusiyyətlərini təsvir edən flaqların toplusu (məsələn, paralelliyi dəstəkləyib-dəstəkləməməsi, nəticə tipini dəyişib-dəyişməməsi və s.).

İmza:

Collector<T, A, R>
  • T — axın elementlərinin tipi,
  • A — aralıq akkumulyatorun tipi,
  • R — nəticənin tipi.

2. Nümunə: MultiMap (Map<K, List<V>>) üçün Collector

Tutaq ki, siz Pair<K, V> cütləri axınını Map<K, List<V>> (multi-xəritə) şəklində toplamaq istəyirsiniz; burada hər açara dəyərlər siyahısı uyğun gəlir.

Reallaşdırma nümunəsi:

public static <K, V> Collector<Pair<K, V>, ?, Map<K, List<V>>> toMultiMap() {
    return Collector.of(
        HashMap::new, // supplier
        (map, pair) -> map.computeIfAbsent(pair.key(), k -> new ArrayList<>()).add(pair.value()), // accumulator
        (map1, map2) -> { // combiner
            map2.forEach((k, vList) -> map1.merge(k, vList, (l1, l2) -> { l1.addAll(l2); return l1; }));
            return map1;
        },
        Function.identity(), // finisher
        Collector.Characteristics.UNORDERED
    );
}

İstifadə:

List<Pair<String, Integer>> pairs = List.of(
    new Pair<>("a", 1), new Pair<>("b", 2), new Pair<>("a", 3)
);

Map<String, List<Integer>> multiMap = pairs.stream().collect(toMultiMap());
// multiMap: {a=[1, 3], b=[2]}

3. Nümunə: top-N elementlər üçün Collector

Tutaq ki, axını ən böyük N elementdən ibarət siyahıya toplamaq istəyirsiniz (məsələn, azalan sırada top-5).

Reallaşdırma:

public static <T> Collector<T, ?, List<T>> topN(int n, Comparator<? super T> comparator) {
    return Collector.of(
        () -> new PriorityQueue<>(n, comparator), // supplier
        (pq, t) -> {
            pq.offer(t);
            if (pq.size() > n) pq.poll(); // ən kiçiyini çıxarırıq
        },
        (pq1, pq2) -> {
            pq2.forEach(t -> {
                pq1.offer(t);
                if (pq1.size() > n) pq1.poll();
            });
            return pq1;
        },
        pq -> {
            List<T> result = new ArrayList<>(pq);
            result.sort(comparator.reversed()); // azalan sırada
            return result;
        },
        Collector.Characteristics.UNORDERED
    );
}

İstifadə:

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

4. Nə vaxt öz Collector-unuzu yazmağa dəyməz

  • Əgər tapşırığı standart kollektorların və downstream əməliyyatlarının (groupingBy, mapping, flatMapping, collectingAndThen və s.) kombinasiyası ilə ifadə etmək mümkündürsə, onları istifadə etmək daha yaxşıdır.
  • Öz Collector yalnız həqiqətən qeyri-standart ssenarilər üçün lazımdır (xüsusi verilənlər strukturu, mürəkkəb aqreqasiya, top-N, multi-xəritələr və s.).
  • Collector-u sadəcə Collector yazmaq xatirinə yazmağa dəyməz — bu, dəstək və testləşdirməni çətinləşdirir.

Nümunə:

// Map<K, Set<V>> üçün öz Collector-unuz əvəzinə:
.collect(Collectors.groupingBy(
    Pair::key,
    Collectors.mapping(Pair::value, Collectors.toSet())
))

5. Xüsusi Spliterator: nə üçün və necə

Spliterator — kolleksiyaları (və ya digər verilənlər mənbələrini) səmərəli şəkildə keçmək və hissələrə bölmək üçün xüsusi interfeysdir; xüsusilə paralel emal üçün uyğundur. Adi iteratordan fərqli olaraq, Spliterator kolleksiyanı paralel emal üçün müstəqil hissələrə “bölə” (split) bilər.

Açar metodlar:

  • tryAdvance(Consumer<? super T> action) — növbəti elementi emal etmək.
  • trySplit() — kolleksiyanı iki hissəyə bölməyə cəhd edir (hissələrdən biri üçün yeni Spliterator qaytarır).
  • estimateSize() — qalan elementlərin sayının təxmini.
  • characteristics() — xüsusiyyətlərin bit maskası (ORDERED, SIZED, SUBSIZED və s.).

trySplit: bölmə strategiyaları

Tarazlaşdırılmış bölmə — paralel stream-lər üçün vacibdir: trySplit təxmini eyni ölçülü hissələr qaytarmalıdır ki, iş yükü bərabər paylansın.

Bölmək mümkün deyilsə (məsələn, elementlər azdırsa) — null qaytarırıq.

Nümunə: faylı hissə-hissə oxumaq üçün Spliterator
Tutaq ki, böyük bir faylınız var və onu hər dəfə 1000 sətir olmaqla (hissələrlə) emal etmək istəyirsiniz ki, hamısını yaddaşda saxlamayasınız.

public class ChunkedLineSpliterator implements Spliterator<List<String>> {
    private final BufferedReader reader;
    private final int chunkSize;

    public ChunkedLineSpliterator(BufferedReader reader, int chunkSize) {
        this.reader = reader;
        this.chunkSize = chunkSize;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<String>> action) {
        List<String> chunk = new ArrayList<>(chunkSize);
        try {
            String line;
            for (int i = 0; i < chunkSize && (line = reader.readLine()) != null; i++) {
                chunk.add(line);
            }
            if (chunk.isEmpty()) return false;
            action.accept(chunk);
            return true;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public Spliterator<List<String>> trySplit() {
        // Fayldan axınla oxumaqda bölmənin mənası yoxdur — null qaytarırıq
        return null;
    }

    @Override
    public long estimateSize() {
        return Long.MAX_VALUE; // əvvəlcədən məlum deyil
    }

    @Override
    public int characteristics() {
        return ORDERED | NONNULL;
    }
}

İstifadə:

try (BufferedReader reader = Files.newBufferedReader(Path.of("big.txt"))) {
    StreamSupport.stream(new ChunkedLineSpliterator(reader, 1000), false)
        .forEach(chunk -> processChunk(chunk));
}

Spliterator-un xüsusiyyətləri

  • ORDERED — elementlər müəyyən ardıcıllıqla gəlir (məsələn, siyahı).
  • SIZED — elementlərin dəqiq sayı məlumdur.
  • SUBSIZEDtrySplit vasitəsilə alınan bütün Spliterator-lar da SIZED-dir.
  • IMMUTABLE — mənbə keçid zamanı dəyişmir.
  • CONCURRENT — mənbə təhlükəsiz paralel modifikasiyanı dəstəkləyir.
  • DISTINCT, SORTED, NONNULL — əlavə xüsusiyyətlər.

Vacibdir: xüsusiyyətləri düzgün göstərmək lazımdır — bu, stream-lərin optimizasiyasına təsir edir.

6. Nümunələr

  • Faylı hissələrlə (chunk-larla) oxuma — böyük faylları hissə-hissə emal etməyə imkan verir, hamısını yaddaşa yükləmədən.
  • Artıq allokasiyalar olmadan parsinq — əgər siz bayt/simvol axınını parsinq edir və müvəqqəti obyektlərin yaradılmasını minimuma endirmək istəyirsinizsə, başlanğıc massivdən “pəncərə” və ya “dilimlər” verən Spliterator reallaşdıra bilərsiniz.

Nümunə: CSV-i sətir-sətir parsinq üçün Spliterator

public class CsvLineSpliterator implements Spliterator<String[]> {
    private final BufferedReader reader;

    public CsvLineSpliterator(BufferedReader reader) {
        this.reader = reader;
    }

    @Override
    public boolean tryAdvance(Consumer<? super String[]> action) {
        try {
            String line = reader.readLine();
            if (line == null) return false;
            action.accept(line.split(","));
            return true;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public Spliterator<String[]> trySplit() {
        return null; // ardıcıl parsinq
    }

    @Override
    public long estimateSize() {
        return Long.MAX_VALUE;
    }

    @Override
    public int characteristics() {
        return ORDERED | NONNULL;
    }
}

7. parallel() ilə inteqrasiya — bunu necə təhlükəsiz etmək olar

  • Əgər Spliterator-unuz paralel bölməni dəstəkləyirsə (trySplit null qaytarmır) və xüsusiyyətlərdə SIZED/SUBSIZED varsa, onda Stream API emalı effektiv şəkildə paralelləşdirə biləcək.
  • Axın tipli mənbələr (fayllar, soketlər) üçün adətən bölmə dəstəklənmir — ardıcıl stream-lərdən istifadə edin.
  • Kolleksiyalar və massivlər üçün — tarazlaşdırılmış bölmə reallaşdırın (məsələn, massivi iki yerə bölün).

Nümunə: massiv üçün Spliterator

public class ArraySpliterator<T> implements Spliterator<T> {
    private final T[] array;
    private int start, end;

    public ArraySpliterator(T[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        if (start < end) {
            action.accept(array[start++]);
            return true;
        }
        return false;
    }

    @Override
    public Spliterator<T> trySplit() {
        int mid = (start + end) >>> 1;
        if (mid == start) return null;
        ArraySpliterator<T> split = new ArraySpliterator<>(array, start, mid);
        start = mid;
        return split;
    }

    @Override
    public long estimateSize() {
        return end - start;
    }

    @Override
    public int characteristics() {
        return ORDERED | SIZED | SUBSIZED | IMMUTABLE;
    }
}

İstifadə:

String[] arr = {"a", "b", "c", "d"};
StreamSupport.stream(new ArraySpliterator<>(arr, 0, arr.length), true)
    .forEach(System.out::println);
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Qədim proqramlaşdırma dilləri kitabxanasının təşkili 📜
Qədim proqramlaşdırma dilləri kitabxanasının təşkili 📜
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Zooloji kvest: heyvanların adının uzunluğuna görə qruplaşdırılması 🦁🐱
Zooloji kvest: heyvanların adının uzunluğuna görə qruplaşdırılması 🦁🐱
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Kolleksiyalarla işi optimallaşdırma
Kolleksiyalarla işi optimallaşdırma
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION