JavaRush /Курстар /All lectures for KK purposes /Ағындармен жұмыс, 1-бөлім

Ағындармен жұмыс, 1-бөлім

All lectures for KK purposes
Деңгей , Сабақ
Қол жетімді

1. Stream типіндегі әдістер тізімі

Stream класы деректер ағындарының тізбектерін оңай құру үшін жасалған. Stream<T> типіндегі объектінің жаңа Stream типіндегі объектілерді қайтаратын әдістері бар.

Әрбір деректер ағыны бір қарапайым әрекет жасай алады, бірақ оларды тізбекке біріктіріп, оған лямбда-функциялар сияқты қызықты нәрселерді қоссаңыз, нәтижесінде өте қуатты нәрсе алуға болады. Жақында өзіңіз де көз жеткізесіз.

Stream класында келесі әдістер бар (ең маңыздысы):

Әдістер Сипаттама
Stream<T> of()
Объектілер жиынтығынан ағын жасайды
Stream<T> generate()
Заданный ереже бойынша ағын жасайды
Stream<T> concat()
Бірнеше ағындарды бірге біріктіреді
Stream<T> filter()
Деректерді сұрыптайды: тек берілген ережеге сәйкес келетін деректерді өткізеді
Stream<T> distinct()
Көшірмелерін жояды: бұрын болған деректерді өткізбейді
Stream<T> sorted()
Деректерді сұрыптайды
Stream<T> peek()
Әрбір дерекке әрекет жасайды
Stream<T> limit(n)
Шектеуге жеткеннен кейін деректерді қиып тастайды
Stream<T> skip(n)
Алғашқы n деректерді өткізіп жібереді
Stream<R> map()
Деректерді бір типтен басқа типке түрлендіреді
Stream<R> flatMap()
Деректерді бір типтен басқа типке түрлендіреді
boolean anyMatch()
Деректер ағынында берілген ережеге сәйкес келетін кем дегенде бір дерек бар екенін тексереді
boolean allMatch()
Ағынның барлық деректерінің берілген ережеге сәйкес келетінін тексереді
boolean noneMatch()
Ағында ешбір деректердің берілген ережеге сәйкес келмейтінін тексереді
Optional<T> findFirst()
Ережеге сәйкес келетін бірінші табылған элементті қайтарады
Optional<T> findAny()
Ережеге сәйкес келетін ағыннан кез келген элементті қайтарады
Optional<T> min()
Ағындағы ең кіші элементті табады
Optional<T> max()
Ағындағы ең үлкен элементті қайтарады
long count()
Ағындағы элементтер санын қайтарады
R collect()
Ағыннан барлық деректерді оқып, оларды коллекция түрінде қайтарады

2. Stream аралық және терминалдық әдістері

Көріп отырғандарыңыздай, жоғарыдағы кестеде көрсетілген барлық әдістер Stream қайтара бермейді. Бұл Stream класының әдістерін аралық (intermediate, non‑terminal) және соңғы (terminal) деп бөлуге болатынымен байланысты.

Аралық әдістер

Аралық әдістер Stream интерфейсін енгізетін объектіні қайтарады және оларды шақыру тізбегіне қоюға болады.

Соңғы әдістер

Соңғы әдістер Stream типінен өзгеше типтегі мәнді қайтарады.

Әдіс шақыру тізбегі

Осылайша, сіз аралық әдістердің кез келген санынан тізбектер жасап, соңында бір соңғы әдісті шақыра аласыз. Бұл көзқарас салыстырмалы түрде күрделі логиканы жүзеге асыруға мүмкіндік береді, сонымен қатар кодтың оқылымын арттырады.

Деректер ағынында деректер мүлде өзгермейді. Аралық әдістер тізбегі – бұл кейбір деректерді өңдеу тізбегін көрсетудің айлакер (декларативті) тәсілі, ол терминалдық әдіс шақырылғаннан кейін басталады.

Яғни, соңғы әдісті шақырмай деректер ағынындағы деректер өңделмейді. Және тек терминалдық әдісті шақырғаннан кейін деректер әдістерді шақыру тізбегі арқылы берілген ережелерге сәйкес өңделе бастайды.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Шақырулар тізбегінің жалпы көрінісі

Аралық және соңғы әдістерді салыстыру:

аралық соңғы
Қайтарылатын мәннің түрі Stream Stream емес
Осы типтегі бірнеше әдістерді біріктіру мүмкіндігі иә жоқ
Бір тізбектегі әдістер саны кез келген бірден артық емес
Соңғы нәтижені шығарады жоқ иә
Деректер ағынындағы деректерді өңдеуді бастайды жоқ иә

Мысал қарап көрейік.

Жануарлар әуесқойлары клубы бар. Ертең оларда "қызыл мысық" күні болады. Клубта әрқайсысының жануарлар тізімі бар иелер бар. Бұл мысықтар ғана емес.

Мақсат: барлық қызыл мысықтардың аттарын таңдап, ертең олар үшін "Кәсіби мерекемен" құттықтау хаттарды басып шығару керек. Ашық хаттар мысықтың жасына қарай сұрыпталуы керек: үлкенінен кішісіне дейін.

Алдымен бұл тапсырманы шешуге арналған көмекші класттарды келтірейік:

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;
   }
}

Енді осы критерийлер бойынша таңдау жүргізілетін Selector класын қарастырайық:

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("Олег Малашков");
      owner1.getPets().addAll(List.of(
            new Cat("Барон", Color.BLACK, 3),
            new Cat("Султан", Color.DARK_GREY, 4),
            new Dog("Эльза", Color.WHITE, 0)
      ));

      final Owner owner2 = new Owner("Дмитрий Васильков");
      owner2.getPets().addAll(List.of(
            new Cat("Рыжик", Color.FOXY, 7),
            new Cat("Барсик", Color.FOXY, 5),
            new Parrot("Адмирал", Color.BLUE, 3)
      ));

      final Owner owner3 = new Owner("Наталия Криж");
      owner3.getPets().addAll(List.of(
            new Dog("Арнольд", Color.FOXY, 3),
            new Pig("Пылесос", Color.LIGHT_GREY, 8)
      ));

      final Owner owner4 = new Owner("Павел Мурахов");
      owner4.getPets().addAll(List.of(
            new Snake("Удав", Color.DARK_GREY, 2)
      ));

      final Owner owner5 = new Owner("Антон Федоренко");
      owner5.getPets().addAll(List.of(
            new Cat("Фишер", Color.BLACK, 16),
            new Cat("Зорро", Color.FOXY, 14),
            new Cat("Марго", Color.WHITE, 3),
            new Cat("Забияка", Color.DARK_GREY, 1)
      ));

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

initData() әдісін шақырып, клубтағы жануарлар иелерінің тізімін деректермен толтыратын main әдісінің кодын жазу қалды және содан кейін қызыл мысықтардың аттарын таңдап, оларды кему ретімен сұрыптайды.

Алдымен, осы тапсырманы шешу үшін stream-дерді қолданбай кодты қарастырайық:

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);
}

Енді баламалы нұсқаны қарап көрейік:

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);
}

Көріп отырғаныңыздай, код әлдеқайда ықшам. Сонымен қатар, stream-дің әрбір жолы – бұл бір әрекет, сондықтан оларды ағылшын тіліндегі сөйлемдер сияқты оқуға болады:

.flatMap(owner -> owner.getPets().stream())
Stream<Owner> -ден Stream<Pet>-ке көшу
.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())
нәтижені тізімге сақтаймыз

3. Ағын жасау

Stream класының әдістерінің ішінде біз әлі қарастырмаған үш әдіс бар. Бұл үш әдістің мақсаты — жаңа ағындар жасау.

Stream<T>.of(T obj) әдісі

of() әдісі бір элементтен тұратын ағынды жасайды. Әдетте, бұл қажет, егер, мысалы, функция Stream<T> типіндегі объектіні параметр ретінде қабылдайды, ал сізде тек T типіндегі объект бар. Сонда сіз оңай және қарапайым түрде бір элементтен тұратын ағынды of() әдісі арқылы ала аласыз.

Мысал:

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

Метод Stream<T> Stream.of(T obj1, T obj2, T obj3, ...)

Метод of() потокты, құрайды, ол берілген элементтермен тұрады. Элементтердің саны кез-келген болуы мүмкін. Мысал:

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

Метод Stream<T> Stream.generate(Supplier<T> obj)

Метод generate() ағынның келесі элементін сұраған кезде оны жасау ережесін беруге мүмкіндік береді. Мысалы, әр уақытта кездейсоқ сан беруге болады.

Мысал:

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

Метод Stream<T> Stream.concat(Stream<T> a, Stream<T> b)

Метод concat() екі берілген ағынды біреуіне біріктіреді. Деректерді оқу кезінде біріншіден кейін екінші ағыннан деректер оқылады. Мысал:

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. Деректерді сүзу

Тағы 6 әдіс жаңа деректер ағындарын жасайды, бұл ағындарды әртүрлі күрделілік тізбектеріне біріктіруге мүмкіндік береді.

Метод Stream<T> filter(Predicate<T>)

Бұл әдіс дереккөз ағынынан деректерді сұрыптайтын берілген ережеге сәйкес жаңа ағынды қайтарады. Әдісті Stream<T> типіндегі объектіде шақыру керек.

Сүзу ережесін беру үшін лямбда-функциясын қолдануға болады, ол содан кейін компилятормен Predicate<T> типіндегі объектке түрленеді.

Мысалдар:

Ағындар тізбегі Түсініктеме
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Тек үштен кіші сандарды қалдырамыз
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Тек нөлден үлкен сандарды қалдырамыз

Метод Stream<T> sorted(Comparator<T>)

Бұл әдіс дереккөз ағынынан деректерді сұрыптайтын жаңа деректер ағынын қайтарады. Параметр ретінде ағынның екі элементін салыстыру ережелерін беретін компараторды беруге болады.

Метод Stream<T> distinct()

Бұл әдіс дереккөз ағынындағы барлық деректерді қамтитын жаңа ағынды қайтарады, тек бірегей деректерді қалдырады. Барлық қайталанатын деректер қалдырылмайды. Мысал:

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

Метод Stream<T> peek(Consumer<T>)

Бұл әдіс жаңа ағынды қайтарады, бірақ ондағы деректер ағынындағы деректер өзгеріссіз қалады. Бірақ ағынның кез-келген элементі сұралғанда, peek() әдісіне берген функциясы шақырылады.

Егер peek() әдісіне System.out::println функциясын берсеңіз, онда барлық объектілер ағыннан өткен кезде экранға шығарылады.

Метод Stream<T> limit(int n)

Бұл әдіс жаңа ағынды қайтарады, ол тек бірінші n деректерді дереккөз ағынынан қамтиды. Қалған деректер қалдырылмайды. Мысал:

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

Метод Stream<T> skip(int n)

Бұл әдіс жаңа ағынды қайтарады, ол дереккөз ағынындағы барлық деректерді қамтиды, бірақ бірінші n деректерді өткізіп жібереді (немесе елемейді). Мысал:

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

Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION