1. Списък с методи на Streamкласа

Класът Streamе създаден, за да улесни конструирането на вериги от потоци от данни. За да постигне това, Stream<T>класът има методи, които връщат нови Streamобекти.

Всеки от тези потоци от данни извършва едно просто действие, но ако ги комбинирате във вериги и добавите интересни ламбда функции , тогава имате мощен механизъм за генериране на изхода, който искате. Скоро ще се убедите сами.

Ето методите на Streamкласа (само най-основните):

Методи Описание
Stream<T> of()
Създава поток от набор от обекти
Stream<T> generate()
Генерира поток според указаното правило
Stream<T> concat()
Свързва два потока
Stream<T> filter()
Филтрира данните, като предава само данни, които отговарят на определеното правило
Stream<T> distinct()
Премахва дубликати. Не предава данни, които вече са бor срещани
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клас

Както можете да видите, не всички методи в tableта по-горе връщат Stream. Това е свързано с факта, че методите на Streamкласа могат да бъдат разделени на междинни (известни също като нетерминални ) методи и терминални методи.

Междинни методи

Междинните методи връщат обект, който имплементира Streamинтерфейса, и те могат да бъдат свързани заедно.

Терминални методи

Терминалните методи връщат стойност, различна от Stream.

Конвейер за извикване на метод

По този начин можете да изградите конвейер на поток, състоящ се от произволен брой междинни методи и едно извикване на терминален метод в края. Този подход ви позволява да приложите доста сложна логика, като същевременно увеличавате четливостта на codeа.

Конвейер за извикване на метод

Данните в поток от данни не се променят изобщо. Верига от междинни методи е гладък (декларативен) начин за указване на конвейер за обработка на данни, който ще бъде изпълнен след извикване на терминалния метод.

С други думи, ако терминалният метод не бъде извикан, тогава данните в потока от данни не се обработват по ниHowъв начин. Едва след като се извика терминалният метод, данните започват да се обработват съгласно правилата, посочени в конвейера на потока.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Общ вид на тръбопровод

Сравнение на междинни и крайни методи:

междинен терминал
Тип връщане Stream не аStream
Може да се комбинира с множество методи от същия тип, за да се образува конвейер да не
Брой методи в един конвейер всяHowви не повече от един
Получава крайния резултат не да
Започва обработка на данните в потока не да

Нека разгледаме един пример.

Да предположим, че имаме клуб за любители на животни. Утре клубът празнува Деня на джинджифиловата котка. Клубът има собственици на домашни любимци, всеки от които има списък с домашни любимци. Те не се ограничават до котките.

Задача: трябва да идентифицирате всички имена на всички джинджифилови котки, за да създадете персонализирани поздравителни картички за тях за утрешния „професионален празник“. Поздравителните картички трябва да бъдат сортирани по възраст на котката, от най-старата към най-младата.

Първо, предоставяме някои класове, които да помогнат за решаването на тази задача:

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класа, където ще бъде напequals изборът според посочените критерии:

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("Ronan Turner");
      owner1.getPets().addAll(List.of(
            new Cat("Baron", Color.BLACK, 3),
            new Cat("Sultan", Color.DARK_GREY, 4),
            new Dog("Elsa", Color.WHITE, 0)
      ));

      final Owner owner2 = new Owner("Scarlet Murray");
      owner2.getPets().addAll(List.of(
            new Cat("Ginger", Color.FOXY, 7),
            new Cat("Oscar", Color.FOXY, 5),
            new Parrot("Admiral", Color.BLUE, 3)
      ));

      final Owner owner3 = new Owner("Felicity Mason");
      owner3.getPets().addAll(List.of(
            new Dog("Arnold", Color.FOXY, 3),
            new Pig("Vacuum Cleaner", Color.LIGHT_GREY, 8)
      ));

      final Owner owner4 = new Owner("Mitchell Stone");
      owner4.getPets().addAll(List.of(
            new Snake("Mr. Boa", Color.DARK_GREY, 2)
      ));

      final Owner owner5 = new Owner("Jonathan Snyder");
      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("Brawler", Color.DARK_GREY, 1)
      ));

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

Остава да добавим code към mainметода. В момента първо извикваме initData()метода, който попълва списъка със собственици на домашни любимци в клуба. След това избираме имената на джинджифиловите котки, сортирани по възраст в низходящ ред.

Първо, нека разгледаме code, който не използва потоци за решаване на тази задача:

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

Както можете да видите, codeът е много по-компактен. В допълнение, всеки ред от тръбопровода на потока е едно действие, така че те могат да се четат като изречения на английски:

.flatMap(owner -> owner.getPets().stream())
Преминете от a Stream<Owner>към aStream<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 метода създават нови потоци от данни, което ви позволява да комбинирате потоци във вериги (or конвейери) с различна сложност.

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()метода, се извиква с него.

Ако предадете функцията System.out::printlnна peek()метода, тогава всички обекти ще бъдат показани, когато преминат през потока.

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