1. Giriş
Stream API ilə kolleksiyalar üzərində işləyərkən, çox vaxt elementləri sadəcə qruplaşdırmaq deyil, eyni zamanda onlarla nəsə etmək lazım olur: çevirmək, filtr etmək, başqa tip kolleksiyaya yığmaq. Bunun üçün Java-da downstream kollektorlar var — hər bir qrup və ya verilənlər hissəsinə tətbiq olunan iç-içə kollektorlar.
Downstream kollektor nədir?
Bu, qruplaşdırma və ya bölmə nəticəsinə tətbiq olunan kollektorudur. Məsələn, groupingBy ilə tələbələri kursa görə qruplaşdıra və hər qrup daxilində yalnız adları və ya yalnız fərqlənənləri (məsələn, GPA üzrə 4.5 həddi ilə) yığa bilərsiniz.
Nümunələr
mapping
Qruplardakı elementləri yığmadan əvvəl çevirməyə imkan verir.
Map<Integer, List<String>> namesByCourse = students.stream()
.collect(Collectors.groupingBy(
Student::getCourse,
Collectors.mapping(Student::getName, Collectors.toList())
));
- Tələbələri kursa görə qruplaşdırırıq.
- Hər qrup üçün yalnız adları yığırıq (bütöv obyektləri deyil).
filtering
Qrup daxilində elementləri filtr etməyə imkan verir (məsələn, GPA >= 4.5 qalsın).
Map<Integer, List<Student>> honorsByCourse = students.stream()
.collect(Collectors.groupingBy(
Student::getCourse,
Collectors.filtering(s -> s.getGpa() >= 4.5, Collectors.toList())
));
Hər qrupda yalnız fərqlənənləri saxlayırıq.
flatMapping
Qruplar daxilində iç-içə kolleksiyaları “açmağa” imkan verir.
Map<String, Set<String>> tagsByAuthor = books.stream()
.collect(Collectors.groupingBy(
Book::getAuthor,
Collectors.flatMapping(
book -> book.getTags().stream(),
Collectors.toSet()
)
));
Hər müəllif üçün onun bütün kitablarının unikal teqlərini yığırıq.
partitioningBy ilə downstream
groupingBy-a bənzər işləyir, lakin boolean şərtinə əsasən iki qrupa ayırır.
Map<Boolean, List<String>> namesByPassed = students.stream()
.collect(Collectors.partitioningBy(
s -> s.getGpa() >= 3.0,
Collectors.mapping(Student::getName, Collectors.toList())
));
Tələbələri uğurla keçənlər və keçməyənlər olaraq bölürük, hər qrupda isə yalnız adlar qalır.
2. teeing: eyni vaxtda iki kollektorla aqreqasiya
Bəzən axın üzrə eyni anda bir neçə aqreqatı hesablamaq lazımdır: məsələn, həm cəmi, həm də ortanı, yaxud minimum və maksimumu. Bunun üçün Java 12+-də teeing kollektoru peyda oldu.
teeing necə işləyir?
Siz iki kollektor və onların nəticələrini birləşdirən funksiyanı verirsiniz.
Sintaksis:
Collectors.teeing(collector1, collector2, (result1, result2) -> ...)
Nümunələr
Minimum + maksimum
Optional<MinMax> minMax = numbers.stream()
.collect(Collectors.teeing(
Collectors.minBy(Integer::compareTo),
Collectors.maxBy(Integer::compareTo),
(min, max) -> min.isPresent() && max.isPresent() ? new MinMax(min.get(), max.get()) : null
));
Minimum və maksimumu eyni anda tapırıq.
Cəm + orta
Result result = numbers.stream()
.collect(Collectors.teeing(
Collectors.summingInt(Integer::intValue),
Collectors.averagingInt(Integer::intValue),
(sum, avg) -> new Result(sum, avg)
));
Cəm və orta dəyər olan bir obyekt alırıq.
Nümunə: maaşlar üzrə hesabat
SalaryStats stats = employees.stream()
.collect(Collectors.teeing(
Collectors.summingInt(Employee::getSalary),
Collectors.averagingInt(Employee::getSalary),
SalaryStats::new
));
SalaryStats daxilində həm cəmi, həm də ortanı saxlayırıq.
3. toUnmodifiableList/Set/Map və kolleksiyanı “dondurmaq” üçün collectingAndThen
Java-nın müasir versiyalarında yaradıldıqdan sonra dəyişdirilə bilməyən — dəyişdirilə bilməyən (unmodifiable) kolleksiyalar mövcuddur. Bu, nəticənin təsadüfən dəyişdirilməməsinin vacib olduğu API-lər üçün rahatdır.
toUnmodifiableList/Set/Map
- Dəyişdirilə bilməyən kolleksiya qaytarırlar.
- Hər hansı element əlavə/çıxarma cəhdi UnsupportedOperationException atacaq.
Nümunələr:
List<String> names = students.stream()
.map(Student::getName)
.collect(Collectors.toUnmodifiableList());
Map<Integer, Student> byId = students.stream()
.collect(Collectors.toUnmodifiableMap(Student::getId, Function.identity()));
collectingAndThen
Kollektorun nəticəsinə funksiyanı tətbiq etməyə imkan verir — məsələn, kolleksiyanı “dondurmaq”.
List<String> names = students.stream()
.map(Student::getName)
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
Əvvəl adi siyahıya yığırıq, sonra onu dəyişdirilə bilməyən edirik.
Set ilə nümunə:
Set<String> tags = books.stream()
.flatMap(book -> book.getTags().stream())
.collect(Collectors.collectingAndThen(
Collectors.toSet(),
Set::copyOf // Java 10+
));
4. Piplayn keisləri
Kəsimlər üzrə hesabatlar və statistika
Təkmil kollektorların köməyi ilə mürəkkəb hesabat və statistikanı “bir sətirdə” qurmaq olar.
Nümunə: şöbələr üzrə orta maaş
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingInt(Employee::getSalary)
));
Nümunə: kateqoriyalar üzrə ən bahalı ilk 3 məhsul
Map<String, List<Product>> top3ByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparing(Product::getPrice).reversed())
.limit(3)
.toList()
)
));
API müqaviləsi kimi dəyişdirilə bilməyən nəticə
Əgər metodunuz dəyişdirilə bilməyən kolleksiya qaytarırsa, bu, təsadüfi səhvlərdən qoruyur və API-ni daha etibarlı edir.
public List<String> getTags() {
return tags.stream()
.collect(Collectors.toUnmodifiableList());
}
İstifadəçi getTags().add("yeni teq") edə bilməyəcək — istisna atılacaq.
Nümunə: bir neçə aqreqatlı hesabat (teeing)
public SalaryReport getSalaryReport(List<Employee> employees) {
return employees.stream()
.collect(Collectors.teeing(
Collectors.averagingInt(Employee::getSalary),
Collectors.summingInt(Employee::getSalary),
SalaryReport::new
));
}
SalaryReport daxilində həm orta, həm də cəmi maaşı saxlayırıq.
5. Tipik səhvlər və incəliklər
Səhv №1: Dəyişdirilməzliyi unutmaq. Əgər adi siyahı qaytarırsınızsa, onu dəyişə bilərlər. Nəticəni “dondurmaq” üçün toUnmodifiableList/Set/Map və ya collectingAndThen istifadə edin.
Səhv №2: Yanlış downstream kollektor. Qrup daxilində elementləri çevirmək lazımdırsa — mapping; filtr etmək lazımdırsa — filtering; iç-içə kolleksiyaları “açmaq” lazımdırsa — flatMapping.
Səhv №3: UnsupportedOperationException. toUnmodifiableList/Set/Map ilə yığılmış və ya collectingAndThen vasitəsilə “dondurulmuş” kolleksiyanı dəyişdirməyə cəhd edərkən yaranır.
Səhv №4: Unikallığın itirilməsi/açarların toqquşması. toUnmodifiableSet unikallıq tələb edir, toUnmodifiableMap isə unikal açarları; əks halda yığma zamanı istisna alacaqsınız.
GO TO FULL VERSION