CodeGym /Kurslar /Java SELF AZ /Axınlarla iş, birinci hissə

Axınlarla iş, birinci hissə

Java SELF AZ
Səviyyə , Dərs
Mövcuddur

1. Stream tipli metodların siyahısı

Stream sinfi, məlumat axını zəncirlərini asanlıqla qurmaq üçün yaradılıb. Bunun üçün Stream<T> tipli obyektin yeni Stream tipli obyektlər qaytaran metodları var.

Bu məlumat axınlarının hər biri yalnız sadə bir əməliyyat yerinə yetirə bilir, lakin onları birləşdirsək və üstəlik buna lambda-funksiyalar kimi maraqlı bir şeyi əlavə etsək, nəticədə çox güclü bir şey əldə edə bilərik. Tezliklə buna özünüz şahid olacaqsınız.

Budur, Stream sinfinin əsas metodlarının siyahısı:

Metodlar Təsvir
Stream<T> of()
Obyektlər dəstindən axın yaradır
Stream<T> generate()
Müəyyən qaydaya əsasən axın yaradır
Stream<T> concat()
Bir neçə axını birləşdirir
Stream<T> filter()
Məlumatları süzgəcdən keçirir: yalnız müəyyən qaydaya uyğun məlumatları buraxır
Stream<T> distinct()
Təkrarlanan məlumatları çıxarır: artıq mövcud olan məlumatları buraxmır
Stream<T> sorted()
Məlumatları sıralayır
Stream<T> peek()
Hər bir məlumat üzərində əməliyyat icra edir
Stream<T> limit(n)
Məlumatları limitdən sonra kəsir
Stream<T> skip(n)
İlk n məlumatı buraxır
Stream<R> map()
Məlumatları bir tipdən digər tipə çevrir
Stream<R> flatMap()
Məlumatları bir tipdən digər tipə çevrir
boolean anyMatch()
Axında müəyyən qaydaya uyğun ən az bir məlumatın olduğunu yoxlayır
boolean allMatch()
Axındakı bütün məlumatların müəyyən qaydaya uyğun olduğunu yoxlayır
boolean noneMatch()
Axında heç bir məlumatın müəyyən qaydaya uyğun olmadığını yoxlayır
Optional<T> findFirst()
Qaydaya uyğun ilk tapılan elementi qaytarır
Optional<T> findAny()
Axında qaydaya uyğun istənilən bir elementi qaytarır
Optional<T> min()
Məlumat axınında minimum elementi tapır
Optional<T> max()
Məlumat axınında maksimum elementi qaytarır
long count()
Məlumat axınındakı elementlərin sayını qaytarır
R collect()
Axından bütün məlumatları oxuyur və onları kolleksiya şəklində qaytarır

2. Intermediate və terminal metodlar Stream

Fərqinə vardığınız kimi, yuxarıdakı cədvəldə göstərilən metodların hamısı Stream qaytarmır. Bu onunla bağlıdır ki, Stream sinfinin metodları intermediate (ara, non‑terminal) və terminal (son) olmaq üzrə iki yerə bölünür.

Ara metodlar

Ara metodlar Stream interfeysini implementasiya edən obyekt qaytarır və onları çağırış zəncirləri şəklində qurmaq olar.

Son metodlar

Son metodlar Stream tipindən fərqli bir tipdə dəyər qaytarır.

Metod çağırışlarının zənciri

Belə bir sistemlə, hər hansı miqdarda ara metodlardan ibarət zəncirlər qurmaq və sonunda bir terminal metodu çağırmaq mümkündür. Bu yanaşma mürəkkəb loqikanı həyata keçirmək üçün faydalıdır və kodun oxunaqlığını artırır.

Məlumat axınında, məlumatlar ümumiyyətlə dəyişmir. Ara metodlardan ibarət zəncir – məlumatların emal ardıcıllığını göstərən bəyannamə formasında bir yoldur. Bu emal ardıcıllığı yalnız terminal (son) metodu çağırdıqdan sonra həyata keçirilir.

Yəni, terminal metodu çağırılmadan, məlumat axınında heç bir emal baş vermir. Yalnız terminal metodu çağırıldıqdan sonra məlumatlar həmin metod çağırışlarının müəyyən etdiyi qaydalarla emal olunmağa başlayır.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Çağırış zəncirinin ümumi görünüşü

Ara və terminal metodların müqayisəsi:

ara son
Qaytarılan dəyərin tipi Stream Stream deyil
Bir neçə metodun bir zəncirdə birləşdirilməsi mümkünlüyü bəli xeyr
Bir zəncirdəki metodların sayı istənilən bir dənə çox deyil
Son nəticə təmin edir xeyr bəli
Axında məlumat emalını işə salır xeyr bəli

Bir misal nəzərdən keçirək.

Heyvansevərlər klubu var. Sabah onların «narıncı pişik günü»dür. Klubda hər birində müxtəlif heyvan siyahısı olan heyvan sahibləri var. Bu, yalnız pişiklər ola bilməz.

Tapşırıq: narıncı pişiklərin adlarını seçmək lazımdır, belə ki, sabah onlar üçün fərdi təbrik açıqcaları hazırlanacaq. Açıqcalar pişiklərin yaşına görə sıralanmalıdır: yaşlıdan cavan pişiyə doğru.

Əvvəlcə bu tapşırığın həlli üçün köməkçi sinifləri təqdim edək:

public enum Color {
   WHITE,
   BLACK,
   DARK_GREY,
   LIGHT_GREY,
   FOXY,
   GREEN,
   YELLOW,
   BLUE,
   MAGENTA
}
public abstract class Animal {
   private String name;
   private Color color;
   private int age;

   public Animal(String name, Color color, int age) {
      this.name = name;
      this.color = color;
      this.age = age;
   }

   public String getName() {
      return name;
   }

   public Color getColor() {
      return color;
   }

   public int getAge() {
      return age;
   }
}
public class Cat extends Animal{
   public Cat(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Dog extends Animal {
   public Dog(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Parrot extends Animal {
   public Parrot(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Pig extends Animal {
   public Pig(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Snake extends Animal {
   public Snake(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Owner {
   private String name;
   private List<Animal> pets = new ArrayList<>();

   public Owner(String name) {
      this.name = name;
   }

   public List<Animal> getPets() {
      return pets;
   }
}

İndi isə Selector sinifinə baxaq, burada yuxarıda göstərilən meyarlara uyğun seçim həyata keçiriləcək:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Selector {
   private static List<Owner> owners;

   private static void initData() {
      final Owner owner1 = new Owner("Oleg Malaşkov");
      owner1.getPets().addAll(List.of(
            new Cat("Baron", Color.BLACK, 3),
            new Cat("Sultan", Color.DARK_GREY, 4),
            new Dog("Elza", Color.WHITE, 0)
      ));

      final Owner owner2 = new Owner("Dmitri Vasilyev");
      owner2.getPets().addAll(List.of(
            new Cat("Rızık", Color.FOXY, 7),
            new Cat("Barsik", Color.FOXY, 5),
            new Parrot("Admiral", Color.BLUE, 3)
      ));

      final Owner owner3 = new Owner("Natiq Kriz");
      owner3.getPets().addAll(List.of(
            new Dog("Arnold", Color.FOXY, 3),
            new Pig("Pilesos", Color.LIGHT_GREY, 8)
      ));

      final Owner owner4 = new Owner("Pavel Murahov");
      owner4.getPets().addAll(List.of(
            new Snake("Uqav", Color.DARK_GREY, 2)
      ));

      final Owner owner5 = new Owner("Anton Fedorenko");
      owner5.getPets().addAll(List.of(
            new Cat("Fisher", Color.BLACK, 16),
            new Cat("Zorro", Color.FOXY, 14),
            new Cat("Margo", Color.WHITE, 3),
            new Cat("Zabiaka", Color.DARK_GREY, 1)
      ));

      owners = List.of(owner1, owner2, owner3, owner4, owner5);
   }
}

Sadəcə main metodunun kodunu tamamlamaq qalır, burada əvvəlcə heyvan sahiblərinin siyahısını məlumatlarla dolduracaq initData() metodu çağırılacaq, sonra narıncı pişiklərin adları seçiləcək və yaşlarına görə azalan qaydada sıralanacaq.

Əvvəlcə bu tapşırığın stream istifadə etməyən koduna baxaq:

public static void main(String[] args) {
   initData();

   List<String> findNames = new ArrayList<>();
   List<Cat> findCats = new ArrayList<>();
   for (Owner owner : owners) {
      for (Animal pet : owner.getPets()) {
         if (Cat.class.equals(pet.getClass()) && Color.FOXY == pet.getColor()) {
            findCats.add((Cat) pet);
         }
      }
   }

   Collections.sort(findCats, new Comparator<Cat>() {
      public int compare(Cat o1, Cat o2) {
         return o2.getAge() - o1.getAge();
      }
   });

   for (Cat cat : findCats) {
      findNames.add(cat.getName());
   }

   findNames.forEach(System.out::println);
}

İndi isə alternativ versiyanı nəzərdən keçirək:

public static void main(String[] args) {
   initData();

   final List<String> findNames = owners.stream()
           .flatMap(owner -> owner.getPets().stream())
           .filter(pet -> Cat.class.equals(pet.getClass()))
           .filter(cat -> Color.FOXY == cat.getColor())
           .sorted((o1, o2) -> o2.getAge() - o1.getAge())
           .map(Animal::getName)
           .collect(Collectors.toList());

   findNames.forEach(System.out::println);
}

Gördüyünüz kimi, kod xeyli kompakt alınıb. Bundan əlavə, stream-in hər sətiri bir hərəkəti göstərir, buna görə onları oxumaq ingiliscə cümlələr kimi mümkündür:

.flatMap(owner -> owner.getPets().stream())
Stream<Owner>-dən Stream<Pet>-ə keçid
.filter(pet -> Cat.class.equals(pet.getClass()))
axında sadəcə pişikləri saxlayırıq
.filter(cat -> Color.FOXY == cat.getColor())
axında sadəcə narıncı rəngliləri saxlayırıq
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
yaşına görə azalan qaydada sıralayırıq
.map(Animal::getName)
adları götürürük
.collect(Collectors.toList())
nəticəni siyahıya yığırıq

3. Stream-lərin yaradılması

Stream sinfinin metodları arasında hələ nəzərdən keçirmədiyimiz üç metod var. Bu üç metodun vəzifəsi - yeni stream-lər yaratmaqdır.

Stream<T>.of(T obj) Metodu

of() metodu bir elementdən ibarət bir stream yaradır. Bu, adətən lazımlıdır, məsələn, funksiyanın parametri olaraq Stream<T> tipində bir obyekt qəbul etməsi tələb olunur, amma sizdə yalnız T tipində bir obyekt var. O zaman, of() metodunun köməyi ilə asanlıqla bir stream yarada bilərsiniz, hansı ki, bir elementdən ibarətdir.

Nümunə:

Stream<Integer> stream = Stream.of(1);

Stream<T> Stream.of(T obj1, T obj2, T obj3, ...) Metodu

of() metodu göstərilən elementlərdən ibarət bir stream yaradır. Elementlərin sayı istənilən qədər ola bilər. Nümunə:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

Stream<T> Stream.generate(Supplier<T> obj) Metodu

generate() metodu ilə bir qayda təyin edə bilərsiniz, hansı ki, stream-dən növbəti elementin istənilməsi zamanı istifadə olunacaq. Məsələn, hər dəfə təsadüfi bir ədəd qaytarmaq olar.

Nümunə:

Stream<Double> s = Stream.generate(Math::random);

Stream<T> Stream.concat(Stream<T> a, Stream<T> b) Metodu

concat() metodu iki göndərilən stream-i birinə birləşdirir. Məlumat oxunan zaman, öncə birinci stream-dən, sonra isə ikinci stream-dən məlumat oxunacaq. Nümunə:

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.of(10, 11, 12, 13, 14);
Stream<Integer> result = Stream.concat(stream1, stream2);

4. Məlumatların filtrasiyası

Bundan başqa, 6 metod yeni məlumat axınları yaradır ki, bu da müxtəlif mürəkkəblikdə axın zəncirlərini birləşdirməyə imkan verir.

Metod Stream<T> filter(Predicate<T>)

Bu metod yeni bir axın qaytarır, bu da mənbə axınındakı məlumatları verilmiş qaydaya uyğun olaraq süzür. Metodu Stream<T> tipli bir obyekt üzərində çağırmaq lazımdır.

Filtrasiya qaydasını təyin etmək üçün lambda-funksiyadan istifadə etmək olar, və bu funksiya kompilator tərəfindən Predicate<T> tipli bir obyektə çevrilir.

Nümunələr:

Axın zəncirləri İzah
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Sadəcə üçdən kiçik ədədləri saxlayırıq
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Sadəcə sıfırdan böyük ədədləri saxlayırıq

Metod Stream<T> sorted(Comparator<T>)

Bu metod, mənbə axınından olan məlumatları sıralayan yeni bir axın qaytarır. Parametr olaraq iki elementin müqayisə qaydasını təyin edən bir Comparator ötürə bilərsiniz.

Metod Stream<T> distinct()

Bu metod yeni bir axın yaradır, bu axın yalnız unikal məlumatları mənbə axınından ehtiva edir. Bütün təkrarlanan məlumatlar atılır. Nümunə:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.distinct(); // 1, 2, 3, 4, 5

Metod Stream<T> peek(Consumer<T>)

Bu metod yeni bir axın yaradır, lakin buradakı məlumatlar mənbə axınındakı kimi eyni olur. Amma axından növbəti element tələb edildiyi zaman, ona siz peek() metoduna ötürdüyünüz funksiya çağırılır.

Əgər peek() metoduna bir funksiya kimi System.out::println ötürülsə, o halda bütün obyektlər ekranda göstəriləcək, məbləğ axından keçdiyi anda.

Metod Stream<T> limit(int n)

Bu metod, yeni bir axın yaradır, bu axın yalnız ilk n məlumatlardan ibarət olur mənbə axınından. Bütün digər məlumatlar atılır. Nümunə:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.limit(3); // 1, 2, 3

Metod Stream<T> skip(int n)

Bu metod yeni bir axın yaradır, bu axın eyni mənbə axını kimi məlumatlara malikdir, lakin ilk n məlumatları n görməzlikdən gəlir (keçir). Nümunə:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.skip(3); // 4, 5, 2, 2, 2, 3, 4

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