CodeGym /Kurslar /JAVA 25 SELF /Stream API-nin əsas əməliyyatları: map, filter, collect

Stream API-nin əsas əməliyyatları: map, filter, collect

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

1. Axın yaradırıq

Stream API-dən istifadə etmək üçün əvvəlcə hər hansı bir kolleksiyadan və ya massivdən axın əldə etmək lazımdır.

Axın yaratma nümunələri

// Siyahıdan
List<String> names = List.of("Anna", "Boris", "Alex", "Alina");
Stream<String> stream = names.stream();

// Massivdən
int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);

// Ayrı-ayrı dəyərlərdən
Stream<String> letters = Stream.of("A", "B", "C");

Qısa olaraq:

  • list.stream() — kolleksiyalar üçün
  • Arrays.stream(array) — massivlər üçün
  • Stream.of(...) — ayrı-ayrı dəyərlər üçün

Tətbiqimiz kontekstində nümunə

Tutaq ki, bizdə istifadəçilərin siyahısı var:

List<String> users = List.of("Ivan", "Anna", "Petr", "Alexey");
Stream<String> userStream = users.stream();

Aralıq və terminal əməliyyatlar

Vacib məqam: Stream API-də əməliyyatlar iki tipə bölünür.

  • Aralıq əməliyyatlar (məsələn, filter, map, distinct) — işləmə mərhələlərini təsvir edir. Onlar yeni axın qaytarır, lakin öz-özlüyündə heç nəyi işə salmır.
  • Terminal əməliyyatlar (məsələn, collect, forEach, count) — konveyeri işə salır və nəticə verir.

Axın “tənbəl” işləyir: terminal əməliyyat çağırılmayana qədər heç bir hesablamalar aparılmır. Məhz buna görə biz tez-tez zənciri collect(...) ilə bitiririk — məhz burada axın yenidən kolleksiyaya və ya digər nəticəyə çevrilir.

2. filter əməliyyatı: elementləri şərtə görə süzgəcdən keçirmək

filter — aralıq əməliyyatdır, verilmiş şərtə uyğun gələn elementləri buraxır.

İmza

Stream<T> filter(Predicate<? super T> predicate); 

Predicate — elementi qəbul edib true (saxla) və ya false (kənar et) qaytaran funksional interfeysdir.

Nümunə: yalnız cüt ədədləri saxlamaq

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4, 6]

Nə baş verir?

  • n -> n % 2 == 0 — lambda ifadəsidir, ədədin 2-yə qalıqsız bölünüb-bölünmədiyini yoxlayır.
  • filter yalnız cüt ədədləri saxlayır.

Nümunə: “A” ilə başlayan adları filtrləyirik

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<String> aNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(aNames); // [Anna, Alex, Alina]

Vacib məqam: filter kolleksiyanı dəyişmir — o, yalnız lazım olan elementlərin olduğu yeni bir axın yaradır.

3. map əməliyyatı: elementi başqa bir şeyə çevirmək

map — çevirmə əməliyyatıdır. O, axının hər bir elementini götürür, ona funksiyanı tətbiq edir və yeni element qaytarır.

İmza

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

Function — elementi qəbul edib nəsə (başqa tip də ola bilər) qaytaran interfeysdir.

Nümunə: sətirlərin uzunluqlarını əldə etmək

List<String> names = List.of("Anna", "Boris", "Alex");

List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());

System.out.println(nameLengths); // [4, 5, 4]

Nə baş verir?

  • map sətiri onun uzunluğuna çevirir (name -> name.length()).
  • Nəticədə ədədlər axını alınır.

Nümunə: sətirləri böyük hərflərə çevirmək

List<String> names = List.of("Anna", "Boris", "Alex");

List<String> upperNames = names.stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

System.out.println(upperNames); // [ANNA, BORIS, ALEX]

4. collect əməliyyatı: nəticəni yenidən kolleksiyaya toplamaq

collect — terminal əməliyyatdır, yəni axının işini tamamlayır və nəticəni kolleksiyaya və ya başqa konteynerə toplayır.

İmza

<R, A> R collect(Collector<? super T, A, R> collector)

Qorxuducu imzadan çəkinməyin! Halların 99 %‑ində siz hazır kollektorları Collectors sinfindən istifadə edirsiniz.

Collectors — “toplayıcılar” dəstinə malik utilit sinifdir. O, axına nəticəni hansı formaya toplamaq lazım olduğunu bildirir: siyahı, çoxluq, sətir və s.

Nümunələr:

  • Collectors.toList() — List-ə
  • Collectors.toSet() — Set-ə
  • Collectors.joining(", ") — vergüllə ayrılmış sətirə

Yəni Collectors — axın elementlərini yerləşdirdiyiniz müxtəlif formalı qutular dəsti kimidir.

Nümunə: nəticəni List-ə toplamaq

List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

Nümunə: nəticəni Set-ə toplamaq

Set<String> uniqueNames = names.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toSet());

Nümunə: sətirləri vergüllə birləşdirmək

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result); // Anna, Boris, Alex

5. Əməliyyatlar zənciri: filtrləmə + çevirmə + nəticənin toplanması

Stream API-nin ən böyük gücü — əməliyyatları bir-birinin ardınca zəncirləmək imkanıdır.

Nümunə: “A” ilə başlayan adların uzunluqlarını əldə etmək

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<Integer> aNameLengths = names.stream()
    .filter(name -> name.startsWith("A"))
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(aNameLengths); // [4, 4, 5]

Addım-addım:

  1. .stream() — siyahıdan axın yaradırıq.
  2. .filter(name -> name.startsWith("A")) — yalnız adların "A" ilə başlayanlarını saxlayırıq.
  3. .map(String::length) — hər adı onun uzunluğuna çeviririk.
  4. .collect(Collectors.toList()) — nəticəni siyahıya toplayırıq.

Oxşar imperativ kod

Eyni şey “köhnə üsulla” belə görünərdi:

List<Integer> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) {
        result.add(name.length());
    }
}

Müqayisə edin: Stream API — bir sətir və “nə edirik” kimi oxunur, “necə edirik” kimi yox.

6. Təcrübə: bir neçə qısa tapşırıq

Məşq edək! Bütün nümunələri bir faylda işə sala bilərsiniz — sadəcə verilənləri dəyişin.

Tapşırıq 1: yalnız tək ədədləri saxlayın və onları kvadrata yüksəldin

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);

List<Integer> oddSquares = numbers.stream()
    .filter(n -> n % 2 != 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(oddSquares); // [1, 9, 25, 49]

Tapşırıq 2: sətirlər siyahısından onların ilk hərflərinin siyahısını əldə etmək

List<String> names = List.of("Anna", "Boris", "Alex");

List<Character> initials = names.stream()
    .map(name -> name.charAt(0))
    .collect(Collectors.toList());

System.out.println(initials); // [A, B, A]

Tapşırıq 3: uzunluğu 3-dən böyük olan sətirləri filtrləyin və onları Set-ə toplayın

List<String> words = List.of("cat", "dog", "elephant", "ant", "bear");

Set<String> longWords = words.stream()
    .filter(word -> word.length() > 3)
    .collect(Collectors.toSet());

System.out.println(longWords); // [bear, elephant]

7. filter, map, collect ilə işləyərkən tipik səhvlər

Səhv № 1: collect-i unutdunuz — nəticə yoxdur!
Stream API pəncərə kənarındakı pişik kimi tənbəldir: terminal əməliyyatını (məsələn, collect və ya forEach) çağırmayınca heç nə baş verməyəcək. Əgər yalnız users.stream().filter(...).map(...); yazsanız — bu, heç bir əməliyyat yerinə yetirməyəcək.

Səhv № 2: filter və map yerləri ilə səhv salınıb
Bəzən yeni başlayanlar əvvəlcə map, sonra filter edirlər. Məsələn, names.stream().map(String::length).filter(len -> len > 3) — bu, ədədlər verəcək, sətirlər yox. Əgər sizə uzunluğu 3-dən böyük olan sətirlər lazımdırsa, əvvəl filtrləyin, sonra çevirmə edin.

Səhv № 3: dəyişməzliyi unutmaq
Stream API əməliyyatları ilkin kolleksiyanı dəyişmir! Onlar yeni nəticə qaytarır. List<String> upper = names.stream().map(String::toUpperCase).collect(Collectors.toList()); — bundan sonra names kolleksiyası əvvəlki kimi qalacaq.

Səhv № 4: xarici dəyişən siyahıdan istifadə cəhdi
Belə etmək lazım deyil:

List<String> result = new ArrayList<>();
names.stream().filter(...).forEach(name -> result.add(name));

Daha yaxşısı collect istifadə etməkdir — bu, daha təhlükəsiz və daha qısadır.

Səhv № 5: NullPointerException
Kolleksiyada null elementlər ola bilərsə, onda name.startsWith("A") çağırışı null üçün xəta verəcək. Mümkündürsə, null üçün filtr əlavə edin:

.filter(name -> name != null && name.startsWith("A"))
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION