CodeGym /Kurslar /JAVA 25 SELF /Stream API ilə funksional üslub

Stream API ilə funksional üslub

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

1. İmperativ vs funksional üslub

Gəlin sadə sualdan başlayaq: ümumiyyətlə «funksional üslub» nəyə lazımdır? O, dövrlərlə olan adi, vərdiş etdiyimiz yanaşmadan nə ilə üstündür? Və ümumiyyətlə «Java-da funksional üslub» nə deməkdir?

İmperativ üslub

İmperativ üslub — kompüterə addım-addım necə bir işi görməyi dediyiniz haldır. Məsələn, siyahıdakı sətirlərdən onların uzunluqlarının siyahısını almaq, yalnız tək uzunluqları saxlamaq və azalan sırayla çeşidləmək lazımdırsa, təxminən belə yazırsınız:

List<String> words = Arrays.asList("pişik", "fil", "kərgədan", "tısbağa", "siçan");
List<Integer> lengths = new ArrayList<>();

for (String word : words) {
    int len = word.length();
    if (len % 2 != 0) {
        lengths.add(len);
    }
}
lengths.sort(Comparator.reverseOrder());
System.out.println(lengths); // [7, 5, 5, 3]

Burada biz açıq-aşkar aralıq siyahı yaradırıq, elementləri əllə əlavə edirik, çeşidləyirik — hər şey addım-addım.

Funksional üslub

Funksional üslub — əldə etmək istədiyinizi təsvir etdiyiniz, necə edildiyini isə alətlərin öhdəsinə buraxdığınız haldır. Java-da bu, Stream API vasitəsilə reallaşdırılıb:

List<String> words = Arrays.asList("pişik", "fil", "kərgədan", "tısbağa", "siçan");

List<Integer> result = words.stream()
    .map(String::length)
    .filter(len -> len % 2 != 0)
    .sorted(Comparator.reverseOrder())
    .toList();

System.out.println(result); // [7, 5, 5, 3]

Burada sanki bir «konveyer» qururuq: əvvəlcə sözləri onların uzunluqlarına çeviririk (map), sonra tək olanları süzgəcdən keçiririk (filter), sonra çeşidləyirik (sorted). Bütün bunlar — bir zəncirdə, aşkar aralıq kolleksiyalar və dövrlər olmadan.

Müqayisə: iterativ vs funksional üslub

Adı iş üslubunda dövr yazırıq, onun daxilində addım-addım kompüterə nə etmək lazım olduğunu izah edirik: hər elementdən keç, şərti yoxla, yeni siyahıya əlavə et və ya ekrana çıxar. Kod işləyir, amma daha çox sətir tutur və tapşırıq nə qədər mürəkkəb olsa, oxumaq və dəstəkləmək bir o qədər çətinləşir.

Funksional üslub prosesin özünü deyil, əldə etmək istədiyimizi təsvir etməyə imkan verir. Uzun dövr əvəzinə əməliyyatlar zənciri qururuq: süzgəcdən keçirmək, çevirmək, nəticəni toplamaq. Bu, daha qısa, daha anlaşıqlı olur və səhv riskini azaldır, çünki dəyişən kolleksiyalarla «əl işi» azdır.

Mənfi tərəfi də var. Yeni başlayanlar üçün belə zəncir dolaşıq görünə bilər: bir neçə lambda ard-arda bəzən sadə dövrdən çətin oxunur. Buna görə funksional üslub qısalıq və ifadəlilikdə uduş qazandırır, amma vərdiş və təcrübə tələb edir.

2. Stream API-nin əsas əməliyyatları

Stream API sadəcə «yeni cür dövr» deyil, kolleksiyaları funksional üslubda emal etmək üçün alətlər toplusudur. Gəlin əsas əməliyyatları nəzərdən keçirək.

Stream necə əldə edilir?

List<String> list = List.of("a", "bb", "ccc");
Stream<String> stream = list.stream();

Aralıq əməliyyatlar

  • map — axının elementlərini çevrir
  • filter — elementləri şərtə görə süzgəcdən keçirir
  • flatMap — hər elementi axına çevirir və onları «açar»
  • sorted — çeşidləmə
  • distinct — dublikatları silir
  • limit / skip — elementləri məhdudlaşdırmaq/ötürmək

Terminal əməliyyatlar

  • forEach — hər element üçün əməliyyat yerinə yetirir
  • collect — nəticəni kolleksiyaya toplayır
  • reduce — axını tək bir dəyərə endirir (məsələn, cəmlə)
  • count — elementlərin sayını hesablayır
  • anyMatch, allMatch, noneMatch — şərtlərin yoxlanması

Nümunə: emal zənciri

List<String> names = List.of("Ada", "Boris", "Vika", "Oya", "Dasha");

List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .map(String::toUpperCase)
    .sorted()
    .toList();

System.out.println(filtered); // [BORIS, DASHA, VIKA]

«Konveyer»in vizual sxemi:

[Ada, Boris, Vika, Oya, Dasha]
   | filter (length>3)
[Boris, Vika, Dasha]
   | map (toUpperCase)
[BORIS, VIKA, DASHA]
   | sorted
[BORIS, DASHA, VIKA]
   | toList

Hər bir əməliyyat ilkin kolleksiyanı dəyişmir — yeni axın yaradılır.

3. Axın üzrə icra və tənbəllik

Aralıq və terminal əməliyyatlar

Stream-lərdə iki tip əməliyyat var. Birincilər — aralıq əməliyyatlardır, məsələn, map, filter və ya sorted. Onlar yeni axın qaytarır və sanki nəsə edəcəklərini «vəd edirlər», amma hələ heç nə icra olunmur. İkincilər — terminal əməliyyatlardır, məsələn forEach, collect və ya reduce. Məhz onlar bütün emalı həqiqətən işə salır. Əsas məqam odur ki, terminal əməliyyatı çağırmayınca, axın «tənbəl» qalır — hesablamalar başlamır.

Nümunə:

Stream<String> stream = List.of("a", "bb", "ccc").stream()
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    });

System.out.println("forEach-dən əvvəl");
stream.forEach(System.out::println);

Çıxış:

forEach-dən əvvəl
map: a
A
map: bb
BB
map: ccc
CCC

Göründüyü kimi, map yalnız forEach başladıqda icra olunur.

Niyə bu əladır?

Bu yanaşma əlavə xərclər olmadan istənilən qədər uzun çevrilmə zəncirləri qurmağa imkan verir. Axın həqiqətən lazım olanda işləməyə başlayır. Bunun sayəsində çox böyük həcmli verilənləri, hətta sonsuz ardıcıllıqları rahat emal etmək mümkündür. Üstəlik, tənbəllik yaddaşa qənaət etməyə və hesablamaları daha səmərəli yerinə yetirməyə kömək edir.

4. Təcrübə: tapşırıq «Sətirlər → uzunluqlar → tək → azalan»

Gəlin addım-addım bu tapşırığı həll edək: «Sətirlər siyahısından sətirlərin uzunluqlarının siyahısını əldə etmək, yalnız tək uzunluqları saxlamaq, azalan sırada çeşidləmək.»

İmperativ həll

List<String> words = Arrays.asList("pişik", "fil", "kərgədan", "tısbağa", "siçan");
List<Integer> lengths = new ArrayList<>();

for (String word : words) {
    int len = word.length();
    if (len % 2 != 0) {
        lengths.add(len);
    }
}
lengths.sort(Comparator.reverseOrder());
System.out.println(lengths); // [7, 5, 5, 3]

Stream API ilə funksional həll

List<String> words = Arrays.asList("pişik", "fil", "kərgədan", "tısbağa", "siçan");

List<Integer> result = words.stream()
    .map(String::length) // sətirləri onların uzunluqlarına çeviririk
    .filter(len -> len % 2 != 0) // yalnız tək uzunluqları saxlayırıq
    .sorted(Comparator.reverseOrder()) // azalanına görə çeşidləyirik
    .toList(); // List-ə yığırıq (Java 16+)

System.out.println(result); // [7, 5, 5, 3]

İzahlar:

  • map(String::length) — hər sətir üçün onun uzunluğunu alırıq.
  • filter(len -> len % 2 != 0) — yalnız tək uzunluqları saxlayırıq.
  • sorted(Comparator.reverseOrder()) — azalan sırada çeşidləyirik.
  • toList() — axını yeni siyahıya yığırıq.
Analoqiya

Bu, sanki zavodda «lent» qurmaq kimidir: hər mərhələdə detallar yenidən emal olunur və yalnız ən sonda hər şey qutuya yığılır.

5. Başqa nümunələr: map, filter, forEach, collect

Nümunə 1: Süzgə və çap

List<String> names = List.of("Ada", "Boris", "Vika", "Oya", "Dasha");

names.stream()
    .filter(name -> name.contains("a"))
    .forEach(System.out::println);
// Çap edəcək: Ada, Vika, Oya, Dasha

Nümunə 2: Çevirmə və Set-ə toplama

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

System.out.println(upperNames); // [ADA, BORIS, VIKA, OYA, DASHA]

Nümunə 3: Bütün sətirlərin uzunluqlarının cəmini almaq

int totalLength = names.stream()
    .mapToInt(String::length)
    .sum();

System.out.println("Cəmi uzunluq: " + totalLength);

Nümunə 4: Predicate və Function istifadəsi

Predicate<String> longName = name -> name.length() > 4;
Function<String, String> greet = name -> "Salam, " + name + "!";

names.stream()
    .filter(longName)
    .map(greet)
    .forEach(System.out::println);
// Salam, Boris!
// Salam, Dasha!

6. Stream API ilə funksional üslubun xüsusiyyətləri

Dəyişən vəziyyətin olmaması

Stream API üslubu yan təsirsiz «təmiz» funksiyaları təşviq edir. Bu o deməkdir ki, lambda daxilində xarici dəyişənləri dəyişməmək daha yaxşıdır.

Pis:

List<String> result = new ArrayList<>();
names.stream()
    .filter(name -> name.startsWith("A"))
    .forEach(result::add); // yan təsir!

Daha yaxşı:

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

Əməliyyatların kompozisiyası

map, filter, sorted və digər metodları birləşdirərək çox uzun zəncirlər qurmaq olar. Əsas odur ki, həddini aşmayın: əgər zəncir ekranı keçirsə, bəlkə də onu hissələrə bölmək lazımdır.

Hesablamaların tənbəlliyi

Stream API terminal əməliyyata çatmayınca heç nə etmir. Bu, resurslara qənaət etməyə və səmərəli emal boru kəmərləri qurmağa imkan verir.

İlkin kolleksiyaların dəyişməzliyi

Stream ilkin kolleksiyanı dəyişmir! Bütün çevrilmələr yeni axın/kolleksiya qaytarır.

7. Stream API nə vaxt istifadə olunmalı

Stream API bu hallarda əladır:

  • Kolleksiyanı tez emal etmək lazım olanda (süzgə, çevirmə, çeşidləmə).
  • Qısa və oxunaqlı kod tələb olunanda.
  • Aralıq kolleksiyaları əl ilə yaratmaq istəməyəndə.
  • Asanlıqla paralelliyi əlavə etmək lazım olanda (parallelStream() vasitəsilə).

İmperativ üslub bəzən daha məqsədəuyğundur, əgər:

  • Bir neçə iç-içə dövr və şərtlərlə mürəkkəb məntiq lazımdırsa.
  • Kritik hissələrdə maksimal performans vacibdirsə (Stream API bəzən bir az daha yavaşdır).
  • Dəyişən vəziyyətlə işləmək lazımdırsa (məsələn, elementləri «yerində» yeniləmək).

8. Stream API ilə işdə tipik səhvlər

Səhv №1: Kolleksiya toplamaq üçün forEach istifadəsi. Bir çox yeni başlayanlar elementləri yeni kolleksiyaya əlavə etmək üçün forEach-dən istifadə edir. Bu, funksional üslub deyil! Bunun əvəzinə collect və ya toList() istifadə edin.

Səhv №2: Vaxtından əvvəl optimizasiya. Dərhal parallelStream() istifadə etməyə çalışmayın — paralelliyə yalnız həqiqətən böyük kolleksiyalar və CPU-intensiv tapşırıqlar üçün ehtiyac var.

Səhv №3: Stream API ilə mutasiya olunan kolleksiyaların qarışdırılması. Stream API dəyişməz verilənlərlə işi nəzərdə tutur. Lambda daxilində kolleksiya elementlərini dəyişməyin.

Səhv №4: «Nəticənin itirilməsi». Terminal əməliyyatı çağırmağı unutdunuzsa — heç nə baş verməyəcək.

Səhv №5: Həddindən artıq mürəkkəb lambda-lar. Lambda ifadəsi bir-iki sətirdən uzun olduqda — onu mənalı adda ayrıca metoda çıxarın.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION