1. Liste der Methoden der StreamKlasse

Die Klasse wurde erstellt, um die Erstellung von Datenstromketten Streamzu vereinfachen . Um dies zu erreichen, verfügt die Klasse über Methoden, die neue Objekte zurückgeben .Stream<T>Stream

Jeder dieser Datenströme führt eine einfache Aktion aus. Wenn Sie sie jedoch zu Ketten kombinieren und interessante Lambda-Funktionen hinzufügen , verfügen Sie über einen leistungsstarken Mechanismus zum Generieren der gewünschten Ausgabe. Bald werden Sie es selbst sehen.

Hier sind die Methoden der StreamKlasse (nur die grundlegendsten):

Methoden Beschreibung
Stream<T> of()
Erstellt einen Stream aus einer Reihe von Objekten
Stream<T> generate()
Erzeugt einen Stream gemäß der angegebenen Regel
Stream<T> concat()
Verkettet zwei Streams
Stream<T> filter()
Filtert die Daten und gibt nur Daten weiter, die der angegebenen Regel entsprechen
Stream<T> distinct()
Entfernt Duplikate. Daten, die bereits gefunden wurden, werden nicht weitergegeben
Stream<T> sorted()
Sortiert die Daten
Stream<T> peek()
Führt eine Aktion für jedes Element im Stream aus
Stream<T> limit(n)
Gibt einen Stream zurück, der gekürzt ist, sodass er nicht länger als das angegebene Limit ist
Stream<T> skip(n)
Überspringt die ersten n Elemente
Stream<R> map()
Konvertiert Daten von einem Typ in einen anderen
Stream<R> flatMap()
Konvertiert Daten von einem Typ in einen anderen
boolean anyMatch()
Überprüft, ob mindestens ein Element im Stream vorhanden ist, das der angegebenen Regel entspricht
boolean allMatch()
Prüft, ob alle Elemente im Stream der angegebenen Regel entsprechen
boolean noneMatch()
Prüft, ob keines der Elemente im Stream mit der angegebenen Regel übereinstimmt
Optional<T> findFirst()
Gibt das erste gefundene Element zurück, das der Regel entspricht
Optional<T> findAny()
Gibt jedes Element im Stream zurück, das der Regel entspricht
Optional<T> min()
Sucht nach dem minimalen Element im Datenstrom
Optional<T> max()
Gibt das maximale Element im Datenstrom zurück
long count()
Gibt die Anzahl der Elemente im Datenstrom zurück
R collect()
Liest alle Daten aus dem Stream und gibt sie als Sammlung zurück

2. Zwischen- und Endoperationen der StreamKlasse

Wie Sie sehen, geben nicht alle Methoden in der obigen Tabelle ein zurück Stream. Dies hängt damit zusammen, dass die Methoden der Klasse in Zwischenmethoden (auch als nicht-terminal bezeichnet ) und TerminalmethodenStream unterteilt werden können .

Zwischenmethoden

Zwischenmethoden geben ein Objekt zurück, das die StreamSchnittstelle implementiert, und können miteinander verkettet werden.

Terminalmethoden

Terminalmethoden geben einen anderen Wert als a zurück Stream.

Methodenaufrufpipeline

So können Sie eine Stream-Pipeline erstellen, die aus einer beliebigen Anzahl von Zwischenmethoden und einem einzelnen Terminal-Methodenaufruf am Ende besteht. Mit diesem Ansatz können Sie recht komplexe Logik implementieren und gleichzeitig die Lesbarkeit des Codes verbessern.

Methodenaufrufpipeline

Die Daten innerhalb eines Datenstroms ändern sich überhaupt nicht. Eine Kette von Zwischenmethoden ist eine raffinierte (deklarative) Möglichkeit, eine Datenverarbeitungspipeline anzugeben, die nach dem Aufruf der Terminalmethode ausgeführt wird.

Mit anderen Worten: Wenn die Terminalmethode nicht aufgerufen wird, werden die Daten im Datenstrom in keiner Weise verarbeitet. Erst nach dem Aufruf der Terminalmethode beginnt die Verarbeitung der Daten gemäß den in der Stream-Pipeline angegebenen Regeln.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Allgemeines Erscheinungsbild einer Pipeline

Vergleich von Zwischen- und Endmethoden:

dazwischenliegend Terminal
Rückgabetyp Stream keinStream
Kann mit mehreren Methoden desselben Typs zu einer Pipeline kombiniert werden Ja NEIN
Anzahl der Methoden in einer einzelnen Pipeline beliebig nicht mehr als eins
Erzeugt das Endergebnis NEIN Ja
Beginnt mit der Verarbeitung der Daten im Stream NEIN Ja

Schauen wir uns ein Beispiel an.

Angenommen, wir haben einen Club für Tierliebhaber. Morgen feiert der Club den Ginger Cat Day. Der Club hat Haustierbesitzer, von denen jeder eine Liste mit Haustieren hat. Sie sind nicht auf Katzen beschränkt.

Aufgabe: Sie müssen alle Namen aller Ingwerkatzen identifizieren, um für sie personalisierte Grußkarten für den morgigen „Berufsurlaub“ zu erstellen. Die Grußkarten sollten nach dem Alter der Katze sortiert sein, vom ältesten zum jüngsten.

Zunächst stellen wir einige Klassen zur Verfügung, die bei der Lösung dieser Aufgabe helfen:

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

Schauen wir uns nun die SelectorKlasse an, in der die Auswahl nach den angegebenen Kriterien getroffen wird:

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

Was bleibt, ist, der Methode Code hinzuzufügen main. Derzeit rufen wir zunächst die initData()Methode auf, die die Liste der Tierbesitzer im Verein füllt. Dann wählen wir die Namen der rotbraunen Katzen aus, sortiert nach ihrem Alter in absteigender Reihenfolge.

Schauen wir uns zunächst den Code an, der keine Streams verwendet, um diese Aufgabe zu lösen:

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

Schauen wir uns nun eine Alternative an:

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

Wie Sie sehen, ist der Code viel kompakter. Darüber hinaus stellt jede Zeile der Stream-Pipeline eine einzelne Aktion dar, sodass sie wie Sätze auf Englisch gelesen werden können:

.flatMap(owner -> owner.getPets().stream())
Bewegen Sie sich von a Stream<Owner>nach aStream<Pet>
.filter(pet -> Cat.class.equals(pet.getClass()))
Behalten Sie nur Katzen im Datenstrom bei
.filter(cat -> Color.FOXY == cat.getColor())
Behalten Sie nur Ingwerkatzen im Datenstrom bei
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
Sortieren Sie in absteigender Reihenfolge nach Alter
.map(Animal::getName)
Holen Sie sich die Namen
.collect(Collectors.toList())
Tragen Sie das Ergebnis in eine Liste ein

3. Streams erstellen

Die StreamKlasse verfügt über drei Methoden, die wir noch nicht behandelt haben. Der Zweck dieser drei Methoden besteht darin, neue Threads zu erstellen.

Stream<T>.of(T obj)Methode

Die of()Methode erstellt einen Stream , der aus einem einzelnen Element besteht. Dies ist normalerweise erforderlich, wenn beispielsweise eine Funktion ein Stream<T>Objekt als Argument akzeptiert, Sie jedoch nur ein TObjekt haben. of()Dann können Sie die Methode einfach und unkompliziert verwenden, um einen Stream zu erhalten, der aus einem einzelnen Element besteht .

Beispiel:

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

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

Die of()Methode erstellt einen Stream , der aus übergebenen Elementen besteht . Es sind beliebig viele Elemente zulässig. Beispiel:

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

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

Mit der generate()Methode können Sie eine Regel festlegen, die zum Generieren des nächsten Elements des Streams verwendet wird, wenn es angefordert wird. Sie können beispielsweise jedes Mal eine Zufallszahl ausgeben .

Beispiel:

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

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

Die concat()Methode verkettet die beiden übergebenen Streams zu einem . Beim Lesen der Daten werden diese zuerst aus dem ersten Stream und dann aus dem zweiten gelesen. Beispiel:

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. Daten filtern

Weitere sechs Methoden erstellen neue Datenströme und ermöglichen es Ihnen, Streams zu Ketten (oder Pipelines) unterschiedlicher Komplexität zu kombinieren.

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

Diese Methode gibt einen neuen Datenstrom zurück, der den Quelldatenstrom gemäß der übergebenen Regel filtert . Die Methode muss für ein Objekt aufgerufen werden, dessen Typ ist Stream<T>.

Sie können die Filterregel mithilfe einer Lambda-Funktion angeben , die der Compiler dann in ein Objekt umwandelt Predicate<T>.

Beispiele:

Verkettete Streams Erläuterung
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Behalten Sie nur Zahlen unter drei bei
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Behalten Sie nur Zahlen größer als Null bei

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

Diese Methode gibt einen neuen Datenstrom zurück, der die Daten im Quellstrom sortiert . Sie übergeben einen Komparator , der die Regeln für den Vergleich zweier Elemente des Datenstroms festlegt.

Stream<T> distinct()Methode

Diese Methode gibt einen neuen Datenstrom zurück, der nur die eindeutigen Elemente im Quelldatenstrom enthält . Alle doppelten Daten werden verworfen. Beispiel:

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>)Methode

Diese Methode gibt einen neuen Datenstrom zurück , obwohl die darin enthaltenen Daten mit denen im Quellstrom identisch sind. Wenn jedoch das nächste Element aus dem Stream angefordert wird, wird die Funktion, die Sie an die peek()Methode übergeben haben, damit aufgerufen.

Wenn Sie die Funktion System.out::printlnan die peek()Methode übergeben, werden alle Objekte angezeigt, wenn sie den Stream durchlaufen.

Stream<T> limit(int n)Methode

Diese Methode gibt einen neuen Datenstrom zurück, der nur erste nElemente im Quelldatenstrom enthält . Alle anderen Daten werden verworfen. Beispiel:

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)Methode

Diese Methode gibt einen neuen Datenstrom zurück , der dieselben Elemente wie der Quellstrom enthält , aber die ersten Elemente überspringt (ignoriert). nBeispiel:

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