1. Lista metodelor Streamclasei

Clasa Streama fost creată pentru a facilita construirea de lanțuri de fluxuri de date. Pentru a realiza acest lucru, Stream<T>clasa are metode care returnează noi Streamobiecte.

Fiecare dintre aceste fluxuri de date efectuează o acțiune simplă, dar dacă le combinați în lanțuri și adăugați funcții lambda interesante , atunci aveți un mecanism puternic pentru a genera rezultatul dorit. În curând vei vedea singur.

Iată metodele clasei Stream(doar cele mai elementare):

Metode Descriere
Stream<T> of()
Creează un flux dintr-un set de obiecte
Stream<T> generate()
Generează un flux conform regulii specificate
Stream<T> concat()
Concatenează două fluxuri
Stream<T> filter()
Filtrează datele, trimițând numai datele care se potrivesc cu regula specificată
Stream<T> distinct()
Îndepărtează duplicatele. Nu transmite date care au fost deja întâlnite
Stream<T> sorted()
Sortează datele
Stream<T> peek()
Efectuează o acțiune asupra fiecărui element din flux
Stream<T> limit(n)
Returnează un flux care este trunchiat astfel încât să nu depășească limita specificată
Stream<T> skip(n)
Omite primele n elemente
Stream<R> map()
Convertește datele de la un tip la altul
Stream<R> flatMap()
Convertește datele de la un tip la altul
boolean anyMatch()
Verifică dacă există cel puțin un element în flux care se potrivește cu regula specificată
boolean allMatch()
Verifică dacă toate elementele din flux se potrivesc cu regula specificată
boolean noneMatch()
Verifică dacă niciunul dintre elementele din flux nu se potrivește cu regula specificată
Optional<T> findFirst()
Returnează primul element găsit care se potrivește cu regula
Optional<T> findAny()
Returnează orice element din flux care se potrivește cu regula
Optional<T> min()
Caută elementul minim din fluxul de date
Optional<T> max()
Returnează elementul maxim din fluxul de date
long count()
Returnează numărul de elemente din fluxul de date
R collect()
Citește toate datele din flux și le returnează ca o colecție

2. Operații intermediare și terminale de către Streamclasă

După cum puteți vedea, nu toate metodele din tabelul de mai sus returnează un Stream. Acest lucru este legat de faptul că metodele clasei Streampot fi împărțite în metode intermediare (cunoscute și ca non-terminale ) și metode terminale .

Metode intermediare

Metodele intermediare returnează un obiect care implementează Streaminterfața și pot fi înlănțuite împreună.

Metode terminale

Metodele terminale returnează o altă valoare decât un Stream.

Conducta de apeluri de metodă

Astfel, puteți construi o conductă de flux constând din orice număr de metode intermediare și un singur apel de metodă terminal la sfârșit. Această abordare vă permite să implementați o logică destul de complexă, sporind în același timp lizibilitatea codului.

Conducta de apeluri de metodă

Datele din interiorul unui flux de date nu se modifică deloc. Un lanț de metode intermediare este o modalitate inteligentă (declarativă) de a specifica o conductă de procesare a datelor care va fi executată după apelarea metodei terminale.

Cu alte cuvinte, dacă metoda terminalului nu este apelată, atunci datele din fluxul de date nu sunt procesate în niciun fel. Abia după apelarea metodei terminale, datele încep să fie procesate conform regulilor specificate în conducta de flux.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Aspectul general al unei conducte

Comparația metodelor intermediare și terminale:

intermediar Terminal
Tip de returnare Stream nu aStream
Poate fi combinat cu mai multe metode de același tip pentru a forma o conductă da Nu
Numărul de metode dintr-o singură conductă orice nu mai mult de unul
Produce rezultatul final Nu da
Începe procesarea datelor din flux Nu da

Să ne uităm la un exemplu.

Să presupunem că avem un club pentru iubitorii de animale. Mâine clubul sărbătorește Ziua Pisicii Ghimbir. Clubul are proprietari de animale de companie, fiecare având o listă de animale de companie. Nu se limitează la pisici.

Sarcină: trebuie să identifici toate numele tuturor pisicilor ghimbir pentru a crea felicitări personalizate pentru „sărbătoarea profesională” de mâine. Felicitarile trebuie sortate in functie de varsta pisicii, de la cea mai mare la cea mai mica.

În primul rând, oferim câteva clase pentru a ajuta la rezolvarea acestei sarcini:

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

Acum să ne uităm la Selectorclasa, unde selecția se va face conform criteriilor specificate:

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

Ceea ce rămâne este să adăugați cod la mainmetodă. În prezent, numim mai întâi initData()metoda, care populează lista proprietarilor de animale de companie din club. Apoi selectăm numele pisicilor ghimbir sortate după vârsta lor în ordine descrescătoare.

Mai întâi, să vedem codul care nu folosește fluxuri pentru a rezolva această sarcină:

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

Acum să ne uităm la o alternativă:

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

După cum puteți vedea, codul este mult mai compact. În plus, fiecare linie a conductei de flux este o singură acțiune, astfel încât acestea pot fi citite ca propoziții în engleză:

.flatMap(owner -> owner.getPets().stream())
Treceți de la a Stream<Owner>la aStream<Pet>
.filter(pet -> Cat.class.equals(pet.getClass()))
Păstrați numai pisicile în fluxul de date
.filter(cat -> Color.FOXY == cat.getColor())
Păstrați numai pisici de ghimbir în fluxul de date
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
Sortați după vârstă în ordine descrescătoare
.map(Animal::getName)
Obțineți numele
.collect(Collectors.toList())
Puneți rezultatul într-o listă

3. Crearea fluxurilor

Clasa Streamare trei metode pe care nu le-am acoperit încă. Scopul acestor trei metode este de a crea fire noi.

Stream<T>.of(T obj)metodă

Metoda of()creează un flux care constă dintr-un singur element. Acest lucru este de obicei necesar atunci când, de exemplu, o funcție ia un Stream<T>obiect ca argument, dar aveți doar un Tobiect. Apoi puteți utiliza cu ușurință și simplu of()metoda pentru a obține un flux care constă dintr-un singur element .

Exemplu:

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

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

Metoda of()creează un flux care constă din elemente trecute . Este permis orice număr de elemente. Exemplu:

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

Stream<T> Stream.generate(Supplier<T> obj)metodă

Metoda generate()vă permite să setați o regulă care va fi folosită pentru a genera următorul element al fluxului atunci când este solicitat. De exemplu, puteți da un număr aleatoriu de fiecare dată.

Exemplu:

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

Stream<T> Stream.concat(Stream<T> a, Stream<T> b)metodă

Metoda concat()concatenează cele două fluxuri trecute într- unul singur . Când datele sunt citite, acestea sunt citite mai întâi din primul flux și apoi din al doilea. Exemplu:

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. Filtrarea datelor

Alte 6 metode creează noi fluxuri de date, permițându-vă să combinați fluxuri în lanțuri (sau conducte) de complexitate diferită.

Stream<T> filter(Predicate<T>)metodă

Această metodă returnează un nou flux de date care filtrează fluxul de date sursă în conformitate cu regula trecută . Metoda trebuie apelată pe un obiect al cărui tip este Stream<T>.

Puteți specifica regula de filtrare folosind o funcție lambda , pe care compilatorul o va converti apoi într-un Predicate<T>obiect.

Exemple:

Pârâie înlănțuite Explicaţie
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Rețineți numai numerele mai mici de trei
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Rețineți numai numerele mai mari decât zero

Stream<T> sorted(Comparator<T>)metodă

Această metodă returnează un nou flux de date care sortează datele din fluxul sursă . Treceți un comparator , care stabilește regulile pentru compararea a două elemente ale fluxului de date.

Stream<T> distinct()metodă

Această metodă returnează un nou flux de date care conține doar elementele unice din fluxul de date sursă . Toate datele duplicate sunt eliminate. Exemplu:

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>)metodă

Această metodă returnează un nou flux de date , deși datele din acesta sunt aceleași cu cele din fluxul sursă. Dar când următorul element este solicitat din flux, funcția pe care ați transmis-o peek()metodei este apelată cu el.

Dacă treceți funcția System.out::printlnla peek()metodă, atunci toate obiectele vor fi afișate când vor trece prin flux.

Stream<T> limit(int n)metodă

Această metodă returnează un nou flux de date care conține doar primele nelemente din fluxul de date sursă . Toate celelalte date sunt eliminate. Exemplu:

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)metodă

Această metodă returnează un nou flux de date care conține toate aceleași elemente ca și fluxul sursă , dar omite (ignoră) primele nelemente. Exemplu:

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