1. List → Set və əksinə çevrilmə
Gəlin, ümumiyyətlə nə üçün kolleksiyaları çevirdiyimizi xatırlayaq. Real tapşırıqlarda tez-tez bizə lazım olur:
- Siyahıdan unikal elementləri almaq (məsələn, e‑mail siyahısı → unikal ünvanlar dəsti).
- Xəritə (Map) qurmaq, məsələn, adlar siyahısından «ad → adın uzunluğu» xəritəsi əldə etmək.
- Elementləri tək bir sətirdə birləşdirmək (məsələn, səliqəli çıxış üçün).
Əvvəllər bunun üçün dövrlər, şərtlər və müvəqqəti kolleksiyalarla çox kod yazmaq lazım gəlirdi. Stream API ilə hər şey daha sadə və… daha üslublu oldu!
Nümunə: siyahıdan unikal adlar dəsti almaq
Tutaq ki, bizdə adlar siyahısı var (birdən proqramınızda kimsə özünü iki dəfə daxil edib — olur belə!):
List<String> names = List.of("Anna", "Sergey", "Anna", "Mariya", "Ivan", "Sergey");
Məqsədimiz — hər bir adın yalnız bir dəfə göründüyü kolleksiya, yəni dəst (Set) əldə etməkdir. Stream API ilə bunu sözün əsl mənasında bir sətrə etmək olar:
Set<String> uniqueNames = names.stream()
.collect(Collectors.toSet());
System.out.println(uniqueNames);
Çıxış:
[Mariya, Ivan, Anna, Sergey]
(Set-də sıra zəmanətli deyil — təəccüblənməyin, sizdə başqa cür ola bilər.)
Bəs əksinə lazımdırsa: Set → List?
Bəzən əksinə — dəsti siyahıya çevirmək lazımdır (məsələn, çeşidləmək və ya indekslə müraciət etmək üçün):
List<String> namesList = uniqueNames.stream()
.collect(Collectors.toList());
System.out.println(namesList);
2. Map-ə çevrilmə: Collectors.toMap()
Nümunə: adlar siyahısından «ad → adın uzunluğu» Map yaratmaq
Bəzən sadəcə proqramçı yox, əsl xəritəçi olmaq istəyirsən — xəritələr qurursan! Gəlin yoxlayaq:
List<String> names = List.of("Anna", "Sergey", "Mariya", "Ivan");
Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name, // açar — adın özü
name -> name.length() // dəyər — adın uzunluğu
));
System.out.println(nameToLength);
Çıxış:
{Mariya=5, Ivan=4, Anna=4, Sergey=6}
Vacib məqam: açar dublikatları
Əgər ilkin siyahıda eyni adlar varsa, onları Map-ə toplamağa cəhd edəndə IllegalStateException: Duplicate key xətası yaranacaq. Java siz eyni açarla iki dəyər qoymaq istəyəndə bunu xoşlamır.
Dublikatları necə işləməli?
Açarlar üst-üstə düşəndə nə etmək lazım olduğunu göstərə bilərsiniz — məsələn, ilk dəyəri və ya son dəyəri saxlamaq:
List<String> names = List.of("Anna", "Sergey", "Anna", "Mariya", "Ivan", "Sergey");
Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldValue, newValue) -> oldValue // ilk dəyəri saxlamaq
));
System.out.println(nameToLength);
Artıq proqram çökməyəcək və Map-ə hər adın yalnız ilk daxilolması düşəcək.
Nümunə: obyektlərlə Map
Bir az mürəkkəbləşdirək: bizdə istifadəçilər siyahısı var və «ad → istifadəçi» Map qurmaq istəyirik:
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " (" + age + ")";
}
}
// İstifadəçilər siyahısına nümunə
List<User> users = List.of(
new User("Anna", 25),
new User("Sergey", 30),
new User("Mariya", 22)
);
Map<String, User> nameToUser = users.stream()
.collect(Collectors.toMap(
user -> user.name,
user -> user
));
System.out.println(nameToUser);
Çıxış:
{Mariya=Mariya (22), Anna=Anna (25), Sergey=Sergey (30)}
3. Sətirə yığma: Collectors.joining()
Bəzən təkcə kolleksiya toplamaq yox, istifadəçiyə və ya log-a səliqəli sətir vermək istəyirsən. Məsələn, bütün adları vergüllə birləşdirmək:
List<String> names = List.of("Anna", "Sergey", "Mariya", "Ivan");
String result = names.stream()
.collect(Collectors.joining(", "));
System.out.println(result);
Çıxış:
Anna, Sergey, Mariya, Ivan
Prefiks və suffiks əlavə etmək olar
String result = names.stream()
.collect(Collectors.joining(", ", "Siyahı: [", "]"));
System.out.println(result);
Çıxış:
Siyahı: [Anna, Sergey, Mariya, Ivan]
4. Terminal əməliyyatlar: forEach, collect, count, anyMatch, allMatch, noneMatch
forEach metodu
forEach ilə artıq yaxşı tanışıq: bu əməliyyat axının hər elementi üçün hərəkət icra edir.
names.stream().forEach(name -> System.out.println("Salam, " + name + "!"));
collect metodu
Elementləri kolleksiyaya, sətirə və ya digər struktura yığır. Ən çox istifadə olunan əməliyyat — Collectors.toList() və Collectors.toSet() ilə List və ya Set-ə yığmaqdır.
count metodu
Axındakı elementlərin sayını hesablayır.
long count = names.stream()
.filter(name -> name.length() > 4)
.count();
System.out.println("4 hərfdən uzun adların sayı: " + count);
anyMatch, allMatch, noneMatch metodları
Şərtin heç olmasa bir element üçün (anyMatch), hamısı üçün (allMatch) və ya heç biri üçün (noneMatch) ödənilib-ödənilmədiyini yoxlayır.
boolean hasShortName = names.stream()
.anyMatch(name -> name.length() < 4);
System.out.println("Qısa ad var? " + hasShortName);
boolean allLong = names.stream()
.allMatch(name -> name.length() > 3);
System.out.println("Bütün adlar 3 hərfdən uzundur? " + allLong);
boolean noneIvan = names.stream()
.noneMatch(name -> name.equals("Ivan"));
System.out.println("Ivan yoxdur? " + noneIvan);
Çıxış:
Qısa ad var? false
Bütün adlar 3 hərfdən uzundur? true
Ivan yoxdur? false
5. Terminal və ara əməliyyatlar: anlayışları möhkəmləndirək
Ara əməliyyatlar (filter, map, distinct, sorted, limit, skip, peek) — yeni Stream qaytarır, zəncirlər qurmaq olar.
Terminal əməliyyatlar (forEach, collect, count, anyMatch, allMatch, noneMatch, reduce, findFirst, findAny) — axını tamamlayır, daha nəticə olmayacaq!
Zəncir nümunəsi:
List<String> result = users.stream()
.filter(user -> user.age > 20)
.map(user -> user.name.toUpperCase())
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println(result);
Çıxış:
[ANNA, IVAN, MARIYA, SERGEY]
6. Stream vasitəsilə kolleksiyaları çevirmədə tipik səhvlər
Səhv № 1: toMap-da açar dublikatlarının işlənməməsi
İlkin kolleksiyada təkrarlanan açarlar varsa və siz Collectors.toMap()-ı açıq birləşdirmə funksiyası göstərmədən istifadə edirsinizsə, proqram istisna atacaq. Belə hallar üçün həmişə birləşdirmə funksiyasını göstərin:
// Son dəyəri saxlamaq
.toMap(keyMapper, valueMapper, (oldVal, newVal) -> newVal)
Səhv № 2: forEach əvəzinə collect istifadə etmək
Bəzən yeni başlayanlar kolleksiyanı forEach ilə “toplamağa” çalışırlar, məsələn:
List<String> list = new ArrayList<>();
names.stream().forEach(name -> list.add(name)); // İşləyir, amma bu Stream-üsulu deyil!
Daha yaxşısı collect(Collectors.toList()) istifadə etməkdir — bu, daha təhlükəsiz və daha təmizdir.
Səhv № 3: axını təkrar istifadə etmək cəhdi
Axını yalnız bir dəfə istifadə etmək olar. Terminal əməliyyatından (məsələn, collect, forEach) sonra həmin Stream ilə işləməyə cəhd IllegalStateException ilə nəticələnəcək.
Səhv № 4: «yan təsirlər yoxdur» prinsipinin pozulması
Ara əməliyyatlar “təmiz” olmalıdır (xarici dəyişənləri dəyişdirmədən). map və ya filter daxilində axından kənarda nəyisə dəyişmək olmaz.
Səhv № 5: Set və Map-də qaydanın nəzərə alınmaması
Sıra vacibdirsə, uyğun kolleksiyalardan istifadə edin — məsələn, LinkedHashSet, TreeMap — və lazım olan kollektoru göstərin.
GO TO FULL VERSION