16-bo‘lim

Stream API

Oqim yaratish, filter va map, saralash, collect va Collectors, guruhlash, reduce hamda oqimlarning kechiktirilgan tabiati.

🕑 13 daqiqa o‘qish 📄 572 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Sikl va oqim
  2. Oqimning uch bosqichi
  3. Kechiktirilgan bajarish
  4. filter, map, sorted
  5. Yig'uvchi amallar
  6. collect va Collectors
  7. Guruhlash
  8. reduce
  9. Oqim yaratishning boshqa usullari
  10. flatMap
  11. Xulosa

Stream API - to'plamlar ustida so'rov yozish vositasi. Java 8 da qo'shilgan va tilning eng ko'p ishlatiladigan qismlaridan biri.

Oqim bilan "nima kerakligini" yozasiz, "qanday qilishni" emas.

Sikl va oqim #

Talabalar ichidan 80 dan yuqori ball olganlarni topib, ism bo'yicha saralash kerak:

Java
record Talaba(String ism, int ball, String guruh) {
}

Shu turdan foydalanamiz.

Java
import java.util.*;

public class Sikl {
    public static void main(String[] args) {
        List<Talaba> sinf = List.of(
                new Talaba("Husanboy", 92, "A"),
                new Talaba("Malika", 78, "B"),
                new Talaba("Nodira", 85, "A"),
                new Talaba("Aziza", 95, "B"),
                new Talaba("Kamola", 64, "A"));

        List<String> natija = new ArrayList<>();
        for (Talaba t : sinf) {
            if (t.ball() > 80) {
                natija.add(t.ism());
            }
        }
        Collections.sort(natija);

        System.out.println(String.join(", ", natija));
    }
}
Natija
Aziza, Husanboy, Nodira

Endi xuddi shuni oqim bilan:

Java
import java.util.*;
import java.util.stream.*;

public class Oqim {
    public static void main(String[] args) {
        List<Talaba> sinf = List.of(
                new Talaba("Husanboy", 92, "A"),
                new Talaba("Malika", 78, "B"),
                new Talaba("Nodira", 85, "A"),
                new Talaba("Aziza", 95, "B"),
                new Talaba("Kamola", 64, "A"));

        String natija = sinf.stream()
                .filter(t -> t.ball() > 80)
                .map(Talaba::ism)
                .sorted()
                .collect(Collectors.joining(", "));

        System.out.println(natija);
    }
}
Natija
Aziza, Husanboy, Nodira

Oraliq o'zgaruvchi yo'q, maqsad ko'rinib turibdi.

Oqimning uch bosqichi #

Har bir oqim uch qismdan iborat 1. Manba stream(), of(), lines() oqim yaratiladi 2. Oraliq amallar filter, map, sorted, limit yangi oqim qaytaradi 3. Yakuniy amal collect, forEach, count natija qaytaradi Oraliq amallar kechiktirilgan: ular yakuniy amal chaqirilgunicha umuman bajarilmaydi. Yakuniy amal bir marta bajariladi va oqimni yopadi - uni qayta ishlatib bo'lmaydi. Oqim manbani o'zgartirmaydi - u faqat o'qiydi va yangi natija hosil qiladi. Shuning uchun oqim bilan ishlash xavfsiz: asl ro'yxat joyida qoladi.
Yakuniy amal bo'lmasa, hech narsa bajarilmaydi

Kechiktirilgan bajarish #

Java
import java.util.*;
import java.util.stream.*;

public class Kechikish {
    public static void main(String[] args) {
        List<String> sozlar = List.of("bir", "ikki", "uch");

        System.out.println("Oqim yaratildi, lekin yakuniy amal yo'q:");
        Stream<String> oqim = sozlar.stream()
                .filter(s -> {
                    System.out.println("  tekshirilmoqda: " + s);
                    return s.length() > 3;
                });

        System.out.println("Hali hech narsa chiqmadi.");
        System.out.println("Endi count() chaqiramiz:");
        System.out.println("  natija: " + oqim.count());
    }
}
Natija
Oqim yaratildi, lekin yakuniy amal yo'q:
Hali hech narsa chiqmadi.
Endi count() chaqiramiz:
  tekshirilmoqda: bir
  tekshirilmoqda: ikki
  tekshirilmoqda: uch
  natija: 1

Diqqat qiling: "tekshirilmoqda" qatorlari count() chaqirilgandan keyin chiqdi, oqim e'lon qilinganda emas.

Ya'ni filter ichidagi kod oqim qurilganda umuman bajarilmadi - u yakuniy amalni kutib turdi. Bu kechiktirilgan bajarishning aniq isboti.

Oqimni ikki marta ishlatib bo'lmaydi
Java
import java.util.List;
import java.util.stream.Stream;

public class BirMarta {
    public static void main(String[] args) {
        Stream<String> oqim = List.of("bir", "ikki").stream();

        System.out.println("Birinchi marta: " + oqim.count());

        try {
            System.out.println(oqim.count());
        } catch (IllegalStateException x) {
            System.out.println("Ikkinchi marta: " + x.getMessage());
        }
    }
}
Natija
Birinchi marta: 2
Ikkinchi marta: stream has already been operated upon or closed

Yakuniy amal oqimni yopadi. Qayta kerak bo'lsa, manbadan yangi oqim yarating: sinf.stream() ni qaytadan chaqiring.

Shuning uchun oqimni o'zgaruvchida saqlash odatda yomon fikr - uni bir zanjirda yozib, darhol yakunlang.

filter, map, sorted #

Java
import java.util.*;
import java.util.stream.*;

public class Asosiy {
    public static void main(String[] args) {
        List<Talaba> sinf = List.of(
                new Talaba("Husanboy", 92, "A"),
                new Talaba("Malika", 78, "B"),
                new Talaba("Nodira", 85, "A"),
                new Talaba("Aziza", 95, "B"));

        System.out.println("Ismlar: "
                + sinf.stream().map(Talaba::ism).toList());

        System.out.println("85+: "
                + sinf.stream()
                      .filter(t -> t.ball() >= 85)
                      .map(Talaba::ism)
                      .toList());

        System.out.println("Ball bo'yicha: "
                + sinf.stream()
                      .sorted(Comparator
                              .comparingInt(Talaba::ball).reversed())
                      .map(t -> t.ism() + "(" + t.ball() + ")")
                      .toList());

        System.out.println("Birinchi 2 ta: "
                + sinf.stream().limit(2).map(Talaba::ism).toList());

        System.out.println("2 tasini o'tkazib: "
                + sinf.stream().skip(2).map(Talaba::ism).toList());
    }
}
Natija
Ismlar: [Husanboy, Malika, Nodira, Aziza]
85+: [Husanboy, Nodira, Aziza]
Ball bo'yicha: [Aziza(95), Husanboy(92), Nodira(85), Malika(78)]
Birinchi 2 ta: [Husanboy, Malika]
2 tasini o'tkazib: [Nodira, Aziza]

Yig'uvchi amallar #

Java
import java.util.*;
import java.util.stream.*;

public class Yigish {
    public static void main(String[] args) {
        List<Talaba> sinf = List.of(
                new Talaba("Husanboy", 92, "A"),
                new Talaba("Malika", 78, "B"),
                new Talaba("Nodira", 85, "A"),
                new Talaba("Aziza", 95, "B"));

        System.out.println("Soni:      " + sinf.stream().count());
        System.out.println("Yig'indi:  "
                + sinf.stream().mapToInt(Talaba::ball).sum());
        System.out.println("O'rtacha:  "
                + sinf.stream().mapToInt(Talaba::ball).average().orElse(0));
        System.out.println("Eng yuqori: "
                + sinf.stream().mapToInt(Talaba::ball).max().orElse(0));
        System.out.println("Hammasi 70+: "
                + sinf.stream().allMatch(t -> t.ball() >= 70));
        System.out.println("90+ bormi:   "
                + sinf.stream().anyMatch(t -> t.ball() >= 90));
        System.out.println("100 yo'qmi:  "
                + sinf.stream().noneMatch(t -> t.ball() == 100));

        IntSummaryStatistics stat =
                sinf.stream().mapToInt(Talaba::ball).summaryStatistics();
        System.out.println("Statistika: min=" + stat.getMin()
                + ", max=" + stat.getMax()
                + ", o'rtacha=" + stat.getAverage());
    }
}
Natija
Soni:      4
Yig'indi:  350
O'rtacha:  87.5
Eng yuqori: 95
Hammasi 70+: true
90+ bormi:   true
100 yo'qmi:  true
Statistika: min=78, max=95, o'rtacha=87.5
mapToInt nega kerak

Stream<Talaba> da sum() metodi yo'q - u faqat IntStream, LongStream va DoubleStream da mavjud.

mapToInt oqimni primitiv oqimga aylantiradi. Bu ikki foyda beradi:

  1. sum, average, max, summaryStatistics kabi metodlar ochiladi;
  2. Avtoqutilash yo'qoladi - katta hajmda sezilarli tezroq.

Teskarisi ham bor: boxed() primitiv oqimni yana obyekt oqimiga qaytaradi.

collect va Collectors #

Java
import java.util.*;
import java.util.stream.*;

public class Yigmoq {
    public static void main(String[] args) {
        List<Talaba> sinf = List.of(
                new Talaba("Husanboy", 92, "A"),
                new Talaba("Malika", 78, "B"),
                new Talaba("Nodira", 85, "A"));

        List<String> royxat = sinf.stream().map(Talaba::ism).toList();
        Set<String> toplam = sinf.stream()
                .map(Talaba::guruh)
                .collect(Collectors.toCollection(TreeSet::new));
        String matn = sinf.stream()
                .map(Talaba::ism)
                .collect(Collectors.joining(" | ", "[", "]"));
        Map<String, Integer> lugat = sinf.stream()
                .collect(Collectors.toMap(Talaba::ism, Talaba::ball,
                        (a, b) -> a, TreeMap::new));

        System.out.println("Ro'yxat: " + royxat);
        System.out.println("To'plam: " + toplam);
        System.out.println("Matn:    " + matn);
        System.out.println("Lug'at:  " + lugat);
    }
}
Natija
Ro'yxat: [Husanboy, Malika, Nodira]
To'plam: [A, B]
Matn:    [Husanboy | Malika | Nodira]
Lug'at:  {Husanboy=92, Malika=78, Nodira=85}
Collectors.toMap ikkita tuzoqqa ega

Birinchi tuzoq - takroriy kalit. Ikki element bir xil kalit bersa, toMap IllegalStateException tashlaydi. Shuning uchun yuqorida uchinchi argument berilgan: (a, b) -> a - "takror bo'lsa birinchisini qoldir".

Ikkinchi tuzoq - tartib. Argumentsiz toMap HashMap qaytaradi va uning tartibi bashoratli emas (12-bo'lim).

Shuning uchun yuqorida to'rtinchi argument sifatida TreeMap::new berilgan - natija kalit bo'yicha saralangan bo'ladi va har doim bir xil chiqadi.

Xuddi shu sabab toSet() o'rniga toCollection(TreeSet::new) ishlatilgan.

Guruhlash #

Java
import java.util.*;
import java.util.stream.*;

public class Guruhlash {
    public static void main(String[] args) {
        List<Talaba> sinf = List.of(
                new Talaba("Husanboy", 92, "A"),
                new Talaba("Malika", 78, "B"),
                new Talaba("Nodira", 85, "A"),
                new Talaba("Aziza", 95, "B"),
                new Talaba("Kamola", 64, "A"));

        Map<String, List<String>> guruhlar = sinf.stream()
                .collect(Collectors.groupingBy(Talaba::guruh, TreeMap::new,
                        Collectors.mapping(Talaba::ism,
                                Collectors.toList())));

        Map<String, Long> sonlar = sinf.stream()
                .collect(Collectors.groupingBy(Talaba::guruh, TreeMap::new,
                        Collectors.counting()));

        Map<String, Double> ortachalar = sinf.stream()
                .collect(Collectors.groupingBy(Talaba::guruh, TreeMap::new,
                        Collectors.averagingInt(Talaba::ball)));

        Map<Boolean, List<String>> otganlar = sinf.stream()
                .collect(Collectors.partitioningBy(t -> t.ball() >= 80,
                        Collectors.mapping(Talaba::ism,
                                Collectors.toList())));

        System.out.println("Guruhlar:  " + guruhlar);
        System.out.println("Sonlari:   " + sonlar);
        System.out.println("O'rtacha:  " + ortachalar);
        System.out.println("80+ / 80-: " + otganlar);
    }
}
Natija
Guruhlar:  {A=[Husanboy, Nodira, Kamola], B=[Malika, Aziza]}
Sonlari:   {A=3, B=2}
O'rtacha:  {A=80.33333333333333, B=86.5}
80+ / 80-: {false=[Malika, Kamola], true=[Husanboy, Nodira, Aziza]}
groupingBy ham sukut bo'yicha HashMap qaytaradi

Yuqorida ikkinchi argument sifatida TreeMap::new berilgan - aks holda guruhlar tartibi bashoratli bo'lmasdi.

Bu darslikdagi barcha misollar bir xil natija berishi kerak, shuning uchun bunday joylarda tartib har doim oshkora belgilanadi.

O'z kodingizda ham shuni qiling: chiqish tartibi muhim bo'lsa - uni aniq ko'rsating, tasodifga tayanmang.

reduce #

Java
import java.util.*;
import java.util.stream.*;

public class Reduce {
    public static void main(String[] args) {
        List<Integer> sonlar = List.of(1, 2, 3, 4, 5);

        System.out.println("Yig'indi:   "
                + sonlar.stream().reduce(0, Integer::sum));
        System.out.println("Ko'paytma:  "
                + sonlar.stream().reduce(1, (a, b) -> a * b));
        System.out.println("Eng katta:  "
                + sonlar.stream().reduce(Integer::max).orElse(0));

        List<String> sozlar = List.of("Java", "juda", "qiziq");
        System.out.println("Birlashtirish: "
                + sozlar.stream()
                        .reduce("", (a, b) -> a.isEmpty() ? b : a + " " + b));
    }
}
Natija
Yig'indi:   15
Ko'paytma:  120
Eng katta:  5
Birlashtirish: Java juda qiziq
reduce ni kamroq ishlating

reduce kuchli, lekin o'qish qiyin. Ko'p hollarda tayyor metod bor:

reduce bilanYaxshiroq
reduce(0, Integer::sum)mapToInt(...).sum()
reduce(Integer::max)max(...)
Satrlarni birlashtirishCollectors.joining(" ")

reduce ni faqat tayyor metod bo'lmagan holatlarda ishlating - masalan ko'paytma hisoblashda.

Oqim yaratishning boshqa usullari #

Java
import java.util.*;
import java.util.stream.*;

public class Manbalar {
    public static void main(String[] args) {
        System.out.println("of:       " + Stream.of("a", "b", "c").toList());
        System.out.println("range:    "
                + IntStream.range(1, 5).boxed().toList());
        System.out.println("rangeClosed: "
                + IntStream.rangeClosed(1, 5).boxed().toList());
        System.out.println("iterate:  "
                + Stream.iterate(1, n -> n * 2).limit(5).toList());
        System.out.println("massivdan: "
                + Arrays.stream(new int[]{3, 1, 2})
                        .sorted().boxed().toList());
        System.out.println("satrdan:  "
                + "salom dunyo".chars().filter(Character::isLetter).count());
    }
}
Natija
of:       [a, b, c]
range:    [1, 2, 3, 4]
rangeClosed: [1, 2, 3, 4, 5]
iterate:  [1, 2, 4, 8, 16]
massivdan: [1, 2, 3]
satrdan:  10

range oxirgi sonni qo'shmaydi, rangeClosed esa qo'shadi.

flatMap #

Ichma-ich to'plamlarni yoyish uchun:

Java
import java.util.*;
import java.util.stream.*;

public class Yoyish {
    public static void main(String[] args) {
        List<List<String>> guruhlar = List.of(
                List.of("Husanboy", "Nodira"),
                List.of("Malika"),
                List.of("Aziza", "Kamola"));

        System.out.println("map bilan:     "
                + guruhlar.stream().map(List::size).toList());
        System.out.println("flatMap bilan: "
                + guruhlar.stream().flatMap(List::stream).sorted().toList());

        List<String> jumlalar = List.of("bir ikki", "uch to'rt");
        System.out.println("So'zlar: "
                + jumlalar.stream()
                          .flatMap(j -> Arrays.stream(j.split(" ")))
                          .toList());
    }
}
Natija
map bilan:     [2, 1, 2]
flatMap bilan: [Aziza, Husanboy, Kamola, Malika, Nodira]
So'zlar: [bir, ikki, uch, to'rt]
Amaliy topshiriq
  1. Sikl bilan yozilgan filtrlashni oqimga aylantiring.
  2. filter, map va sorted ni zanjirda ishlating.
  3. Oqimni ikki marta ishlatishga urinib, istisnoni ko'ring.
  4. mapToInt bilan yig'indi va o'rtachani hisoblang.
  5. summaryStatistics natijasini chiqaring.
  6. Collectors.joining bilan prefiks va suffiks qo'shing.
  7. toMap da takroriy kalit berib, istisnoni ko'ring.
  8. groupingBy bilan guruhlab, TreeMap::new qo'shing.
  9. partitioningBy bilan ikkiga ajrating.
  10. flatMap bilan ichma-ich ro'yxatni yoying.

Xulosa #

  • Oqim uch qismdan: manba → oraliq amallar → yakuniy amal.
  • Oraliq amallar kechiktirilgan - yakuniy amal bo'lmasa hech narsa bajarilmaydi.
  • Oqimni ikki marta ishlatib bo'lmaydi.
  • Oqim manbani o'zgartirmaydi.
  • mapToInt primitiv oqimga o'tkazadi va sum, average, summaryStatistics ni ochadi.
  • toMap takroriy kalitda istisno tashlaydi - uchinchi argument bering.
  • groupingBy va toMap sukut bo'yicha HashMap qaytaradi - tartib kerak bo'lsa TreeMap::new bering.
  • reduce ni faqat tayyor metod bo'lmaganda ishlating.
  • flatMap ichma-ich to'plamlarni yoyadi.

Keyingi bo'limda Optional va null xavfsizligini ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.