1. Liste des méthodes de la Streamclasse

La Streamclasse a été créée pour faciliter la construction de chaînes de flux de données. Pour ce faire, la Stream<T>classe a des méthodes qui renvoient de nouveaux Streamobjets.

Chacun de ces flux de données effectue une action simple, mais si vous les combinez en chaînes et ajoutez des fonctions lambda intéressantes , vous disposez d'un mécanisme puissant pour générer la sortie souhaitée. Bientôt, vous verrez par vous-même.

Voici les méthodes de la Streamclasse (uniquement les plus basiques):

Méthodes Description
Stream<T> of()
Crée un flux à partir d'un ensemble d'objets
Stream<T> generate()
Génère un flux selon la règle spécifiée
Stream<T> concat()
Concatène deux flux
Stream<T> filter()
Filtre les données, en ne transmettant que les données qui correspondent à la règle spécifiée
Stream<T> distinct()
Supprime les doublons. Ne transmet pas les données déjà rencontrées
Stream<T> sorted()
Trie les données
Stream<T> peek()
Effectue une action sur chaque élément du flux
Stream<T> limit(n)
Renvoie un flux qui est tronqué de sorte qu'il ne dépasse pas la limite spécifiée
Stream<T> skip(n)
Ignore les n premiers éléments
Stream<R> map()
Convertit les données d'un type à un autre
Stream<R> flatMap()
Convertit les données d'un type à un autre
boolean anyMatch()
Vérifie s'il y a au moins un élément dans le flux qui correspond à la règle spécifiée
boolean allMatch()
Vérifie si tous les éléments du flux correspondent à la règle spécifiée
boolean noneMatch()
Vérifie si aucun des éléments du flux ne correspond à la règle spécifiée
Optional<T> findFirst()
Renvoie le premier élément trouvé qui correspond à la règle
Optional<T> findAny()
Renvoie tout élément du flux qui correspond à la règle
Optional<T> min()
Recherche l'élément minimum dans le flux de données
Optional<T> max()
Renvoie l'élément maximum dans le flux de données
long count()
Renvoie le nombre d'éléments dans le flux de données
R collect()
Lit toutes les données du flux et les renvoie sous forme de collection

2. Opérations intermédiaires et terminales par la Streamclasse

Comme vous pouvez le voir, toutes les méthodes du tableau ci-dessus ne renvoient pas un Stream. Ceci est lié au fait que les méthodes de la Streamclasse peuvent être divisées en méthodes intermédiaires (également appelées non-terminales ) et en méthodes terminales .

Méthodes intermédiaires

Les méthodes intermédiaires renvoient un objet qui implémente l' Streaminterface, et elles peuvent être chaînées.

Méthodes terminales

Les méthodes terminales renvoient une valeur autre que a Stream.

Pipeline d'appel de méthode

Ainsi, vous pouvez créer un pipeline de flux composé de n'importe quel nombre de méthodes intermédiaires et d'un seul appel de méthode de terminal à la fin. Cette approche vous permet d'implémenter une logique plutôt complexe, tout en augmentant la lisibilité du code.

Pipeline d'appel de méthode

Les données à l'intérieur d'un flux de données ne changent pas du tout. Une chaîne de méthodes intermédiaires est une manière astucieuse (déclarative) de spécifier un pipeline de traitement de données qui sera exécuté après l'appel de la méthode terminale.

En d'autres termes, si la méthode de terminal n'est pas appelée, les données du flux de données ne sont en aucun cas traitées. Ce n'est qu'après l'appel de la méthode de terminal que les données commencent à être traitées conformément aux règles spécifiées dans le pipeline de flux.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Aspect général d'un pipeline

Comparaison des méthodes intermédiaires et terminales :

intermédiaire Terminal
Type de retour Stream pas unStream
Peut être combiné avec plusieurs méthodes du même type pour former un pipeline Oui Non
Nombre de méthodes dans un seul pipeline n'importe quel pas plus d'un
Produit le résultat final Non Oui
Démarre le traitement des données dans le flux Non Oui

Prenons un exemple.

Supposons que nous ayons un club pour les amoureux des animaux. Demain, le club célèbre le Ginger Cat Day. Le club a des propriétaires d'animaux de compagnie, chacun ayant une liste d'animaux de compagnie. Ils ne se limitent pas aux chats.

Tâche : vous devez identifier tous les noms de tous les chats roux afin de leur créer des cartes de vœux personnalisées pour les " vacances professionnelles " de demain. Les cartes de vœux doivent être triées par âge du chat, du plus vieux au plus jeune.

Tout d'abord, nous fournissons quelques classes pour vous aider à résoudre cette tâche :

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

Regardons maintenant la Selectorclasse, où la sélection sera faite selon les critères spécifiés :

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

Il ne reste plus qu'à ajouter du code à la mainméthode. Actuellement, nous appelons d'abord la initData()méthode, qui remplit la liste des propriétaires d'animaux du club. Ensuite, nous sélectionnons les noms des chats roux triés par leur âge par ordre décroissant.

Examinons d'abord le code qui n'utilise pas de flux pour résoudre cette tâche :

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

Voyons maintenant une alternative :

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

Comme vous pouvez le voir, le code est beaucoup plus compact. De plus, chaque ligne du pipeline de flux est une action unique, elles peuvent donc être lues comme des phrases en anglais :

.flatMap(owner -> owner.getPets().stream())
Passer d'un Stream<Owner>à unStream<Pet>
.filter(pet -> Cat.class.equals(pet.getClass()))
Conserver uniquement les chats dans le flux de données
.filter(cat -> Color.FOXY == cat.getColor())
Ne conserver que les chats roux dans le flux de données
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
Trier par âge dans l'ordre décroissant
.map(Animal::getName)
Obtenez les noms
.collect(Collectors.toList())
Mettre le résultat dans une liste

3. Création de flux

La Streamclasse a trois méthodes que nous n'avons pas encore couvertes. Le but de ces trois méthodes est de créer de nouveaux threads.

Stream<T>.of(T obj)méthode

La of()méthode crée un flux composé d'un seul élément. Ceci est généralement nécessaire lorsque, par exemple, une fonction prend un Stream<T>objet comme argument, mais que vous n'avez qu'un Tobjet. Ensuite, vous pouvez facilement et simplement utiliser la of()méthode pour obtenir un flux composé d'un seul élément .

Exemple:

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

Stream<T> Stream.of(T obj1, T obj2, T obj3, ...)méthode

La of()méthode crée un flux composé d' éléments passés . N'importe quel nombre d'éléments est autorisé. Exemple:

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

Stream<T> Stream.generate(Supplier<T> obj)méthode

La generate()méthode vous permet de définir une règle qui sera utilisée pour générer l'élément suivant du flux lorsqu'il est demandé. Par exemple, vous pouvez donner un nombre aléatoire à chaque fois.

Exemple:

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

Stream<T> Stream.concat(Stream<T> a, Stream<T> b)méthode

La concat()méthode concatène les deux flux passés en un seul . Lorsque les données sont lues, elles sont d'abord lues à partir du premier flux, puis à partir du second. Exemple:

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. Filtrage des données

6 autres méthodes créent de nouveaux flux de données, vous permettant de combiner des flux en chaînes (ou pipelines) de complexité variable.

Stream<T> filter(Predicate<T>)méthode

Cette méthode renvoie un nouveau flux de données qui filtre le flux de données source en fonction de la règle transmise . La méthode doit être appelée sur un objet dont le type est Stream<T>.

Vous pouvez spécifier la règle de filtrage à l'aide d'une fonction lambda , que le compilateur convertira ensuite en Predicate<T>objet.

Exemples:

Flux chaînés Explication
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Ne retenir que les nombres inférieurs à trois
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Ne conserver que les nombres supérieurs à zéro

Stream<T> sorted(Comparator<T>)méthode

Cette méthode renvoie un nouveau flux de données qui trie les données dans le flux source . Vous transmettez un comparator , qui définit les règles de comparaison de deux éléments du flux de données.

Stream<T> distinct()méthode

Cette méthode renvoie un nouveau flux de données contenant uniquement les éléments uniques du flux de données source . Toutes les données en double sont supprimées. Exemple:

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>)méthode

Cette méthode renvoie un nouveau flux de données , même si les données qu'il contient sont les mêmes que celles du flux source. Mais lorsque l'élément suivant est demandé au flux, la fonction que vous avez transmise à la peek()méthode est appelée avec lui.

Si vous passez la fonction System.out::printlnà la peek()méthode, alors tous les objets seront affichés lors de leur passage dans le flux.

Stream<T> limit(int n)méthode

Cette méthode renvoie un nouveau flux de données contenant uniquement les premiers néléments du flux de données source . Toutes les autres données sont supprimées. Exemple:

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)méthode

Cette méthode renvoie un nouveau flux de données qui contient tous les mêmes éléments que le flux source , mais saute (ignore) les premiers néléments. Exemple:

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