Java 8 Stream – Flux Java

Bienvenue au tutoriel sur l’API Java 8 Stream. Dans les derniers articles sur Java 8, nous avons examiné les Changements d’interface Java 8 et les Interfaces fonctionnelles et expressions lambda. Aujourd’hui, nous allons nous pencher sur l’une des principales API introduites dans Java 8 – Java Stream.

Java 8 Stream

  1. Java 8 Stream
  2. Collections et Java Stream
  3. Interfaces fonctionnelles dans Java 8 Stream
    1. Fonction et BiFunction
    2. Prédicat et BiPrédicat
    3. Consommateur et BiConsommateur
    4. Fournisseur
  4. java.util.Optional
  5. java.util.Spliterator
  6. Opérations intermédiaires et terminales de Java Stream
  7. Opérations de dérivation rapide de Java Stream
  8. Exemples de Java Stream
    1. Création de flux Java
    2. Conversion d’un flux Java en collection ou tableau
    3. Opérations intermédiaires de Java Stream
    4. Opérations terminales de Java Stream
  9. Limitations de l’API Java 8 Stream

Java Stream

Avant d’examiner les exemples de l’API Java Stream, voyons pourquoi elle était nécessaire. Supposons que nous voulions itérer sur une liste d’entiers et trouver la somme de tous les entiers supérieurs à 10. Avant Java 8, l’approche consistait à :

private static int sumIterator(List<Integer> list) {
	Iterator<Integer> it = list.iterator();
	int sum = 0;
	while (it.hasNext()) {
		int num = it.next();
		if (num > 10) {
			sum += num;
		}
	}
	return sum;
}

Il y a trois problèmes majeurs avec l’approche ci-dessus :

  1. Nous voulons simplement connaître la somme des entiers, mais nous devons également spécifier comment se déroulera l’itération. Cela est également appelé itération externe car le programme client gère l’algorithme pour itérer sur la liste.
  2. Le programme est de nature séquentielle, il n’y a aucun moyen de le faire facilement en parallèle.
  3. Il y a beaucoup de code même pour une tâche simple.

Pour surmonter tous ces inconvénients, Java 8 Stream API a été introduite. Nous pouvons utiliser Java Stream API pour mettre en œuvre l’itération interne, qui est meilleure car le framework Java contrôle l’itération. L’itération interne offre plusieurs fonctionnalités telles que l’exécution séquentielle et parallèle, le filtrage basé sur les critères donnés, la cartographie, etc. La plupart des arguments des méthodes de l’API Java 8 Stream sont des interfaces fonctionnelles, donc les expressions lambda fonctionnent très bien avec elles. Voyons comment nous pouvons écrire la logique ci-dessus en une seule instruction en utilisant Java Streams.

private static int sumStream(List<Integer> list) {
	return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}

Remarquez que le programme ci-dessus utilise la stratégie d’itération, les méthodes de filtrage et de cartographie du framework Java et augmenterait l’efficacité. Tout d’abord, nous examinerons les concepts de base de l’API Java 8 Stream, puis nous passerons en revue quelques exemples pour comprendre les méthodes les plus couramment utilisées.

Les collections et Java Stream

A collection is an in-memory data structure to hold values and before we start using collection, all the values should have been populated. Whereas a java Stream is a data structure that is computed on-demand. Java Stream doesn’t store data, it operates on the source data structure (collection and array) and produce pipelined data that we can use and perform specific operations. Such as we can create a stream from the list and filter it based on a condition. Java Stream operations use functional interfaces, that makes it a very good fit for functional programming using lambda expression. As you can see in the above example that using lambda expressions make our code readable and short. Java 8 Stream internal iteration principle helps in achieving lazy-seeking in some of the stream operations. For example filtering, mapping, or duplicate removal can be implemented lazily, allowing higher performance and scope for optimization. Java Streams are consumable, so there is no way to create a reference to stream for future usage. Since the data is on-demand, it’s not possible to reuse the same stream multiple times. Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections. All the Java Stream API interfaces and classes are in the java.util.stream package. Since we can use primitive data types such as int, long in the collections using auto-boxing and these operations could take a lot of time, there are specific classes for primitive types – IntStream, LongStream and DoubleStream.

Interfaces Fonctionnelles dans Java 8 Stream

Quelques-unes des interfaces fonctionnelles couramment utilisées dans les méthodes de l’API Stream de Java 8 sont :

  1. Function et BiFunction : Function représente une fonction qui prend un type d’argument et renvoie un autre type d’argument. Function<T, R> est la forme générique où T est le type de l’entrée de la fonction et R est le type du résultat de la fonction. Pour manipuler les types primitifs, il existe des interfaces Function spécifiques – ToIntFunction, ToLongFunction, ToDoubleFunction, ToIntBiFunction, ToLongBiFunction, ToDoubleBiFunction, LongToIntFunction, LongToDoubleFunction, IntToLongFunction, IntToDoubleFunction, etc. Certaines des méthodes Stream où Function ou sa spécialisation primitive sont utilisées sont :
    • <R> Stream<R> map(Function<? super T, ? extends R> mapper)
    • IntStream mapToInt(ToIntFunction<? super T> mapper) – de même pour les types long et double renvoyant un flux spécifique aux types primitifs.
    • IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) – de même pour les types long et double
    • <A> A[] toArray(IntFunction<A[]> generator)
    • <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  2. Prédicat et BiPredicate: Représente un prédicat contre lequel les éléments du flux sont testés. Cela est utilisé pour filtrer les éléments du flux Java. Tout comme Function, il existe des interfaces spécifiques aux types primitifs pour int, long et double. Certaines des méthodes de flux où des spécialisations Predicate ou BiPredicate sont utilisées sont :
    • Flux<T> filter(Predicate<? super T> predicate)
    • boolean anyMatch(Predicate<? super T> predicate)
    • boolean allMatch(Predicate<? super T> predicate)
    • boolean noneMatch(Predicate<? super T> predicate)
  3. Consommateur et BiConsommateur: Il représente une opération qui accepte un seul argument d’entrée et ne retourne aucun résultat. Il peut être utilisé pour effectuer une action sur tous les éléments du flux Java. Certains des méthodes du flux Java 8 où les interfaces Consommateur, BiConsommateur ou leurs spécialisations primitives sont utilisées sont:
    • Stream<T> peek(Consumer<? super T> action)
    • void forEach(Consumer<? super T> action)
    • void forEachOrdered(Consumer<? super T> action)
  4. Fournisseur: Le fournisseur représente une opération par laquelle nous pouvons générer de nouvelles valeurs dans le flux. Certaines des méthodes dans Stream qui prennent un argument Fournisseur sont:
    • public static<T> Stream<T> generate(Supplier<T> s)
    • <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)

java.util.Optional

Java Optional est un objet conteneur qui peut contenir ou non une valeur non nulle. Si une valeur est présente, isPresent() renverra true et get() renverra la valeur. Les opérations terminales du Stream renvoient un objet Optional. Certaines de ces méthodes sont :

  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • Optional<T> min(Comparator<? super T> comparator)
  • Optional<T> max(Comparator<? super T> comparator)
  • Optional<T> findFirst()
  • Optional<T> findAny()

java.util.Spliterator

Pour prendre en charge l’exécution parallèle dans l’API Stream de Java 8, l’interface Spliterator est utilisée. La méthode trySplit de Spliterator renvoie un nouveau Spliterator qui gère un sous-ensemble des éléments du Spliterator d’origine.

Opérations intermédiaires et terminales de Java Stream

Les opérations Java Stream API qui renvoient un nouveau Stream sont appelées opérations intermédiaires. La plupart du temps, ces opérations sont paresseuses, elles commencent donc à produire de nouveaux éléments de flux et les envoient à l’opération suivante. Les opérations intermédiaires ne sont jamais des opérations produisant le résultat final. Les opérations intermédiaires couramment utilisées sont filter et map. Les opérations Java 8 Stream API qui renvoient un résultat ou produisent un effet secondaire. Une fois que la méthode terminale est appelée sur un flux, elle consomme le flux et après cela, nous ne pouvons plus utiliser le flux. Les opérations terminales sont avides, c’est-à-dire qu’elles traitent tous les éléments dans le flux avant de renvoyer le résultat. Les méthodes terminales couramment utilisées sont forEach, toArray, min, max, findFirst, anyMatch, allMatch, etc. Vous pouvez identifier les méthodes terminales par le type de retour, elles ne renverront jamais un Stream.

Opérations de court-circuitage Java Stream

Une opération intermédiaire est appelée court-circuitage si elle peut produire un flux fini pour un flux infini. Par exemple, limit() et skip() sont deux opérations intermédiaires de court-circuitage. Une opération terminale est appelée court-circuitage si elle peut se terminer en temps fini pour un flux infini. Par exemple, anyMatch, allMatch, noneMatch, findFirst et findAny sont des opérations terminales de court-circuitage.

Exemples de Java Stream

I have covered almost all the important parts of the Java 8 Stream API. It’s exciting to use this new API features and let’s see it in action with some java stream examples.

Création de flux Java

Il existe plusieurs façons de créer un flux< Java à partir de tableauxdi et de collections. Examinons-lesy avec des exemples simples.

N>ous pouvons utiliser Stream.of() pour créer un flux àles partir de données de type similaire. Par de exemple, nous flux pouv Java Stream&ltyInteger> flux =3 Stream.of(1,2><,3,4);

  1. Nous4 pouvons utiliser <>Stream.of() pour créer undi flux à partir dey données de type similaire. Par exemple,4 nous pouvons créer un> flux Java d’entiers à partir d’unCr groupe d’objets inté ou Integer.

    N deous pouvons utiliser Stream.of flux() avec un tableau d’objets Java pour retourner le flux<. Notediz qu’il ney prend pas en charge l5’autoboxing, nous>

    Stream&lt5Integer> flux => Stream.of(new Integer[]{1Il,2,3,4 existe}); 
    //fonctionne bien
    
    Stream< plusieurs;Integer> flux faç1 = Stream.ofons(new int[]{1,2,3,4 de}); 
    //Erreur de compilation, Incompatibilit créeré de type: impossible de convertir de Stream un<int[]> en Stream< fluxInteger>
    

    Nous pouvons table utiliser Stream.ofaux() avec un tableau et d’objets pour retour dener le flux. Note collectionsz qu’il ne prend pas. en charge l’autoboxing, nous ne pou Exvons donc pas passer deamin tableau de types primitifsons.//fonctionne bien//Erreur de compilation, Incompatibilité de avec type: impossible de des convertir exemp de Streamles en Stream.<6>

    Nous pouvons utiliser Stream.of() pour créer un flux à partir de données de type similaire. Par exemple, nous pouvons créer un flux Java d’entiers à partir d’un groupe d’entiers ou d’objets Integer.

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

  2. Nous pouvons utiliser Stream.of() avec un tableau d’objets pour retourner le flux. Notez qu’il ne prend pas en charge l’autoboxing, nous ne pouvons donc pas passer de tableau de types primitifs.

    Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); 
    // fonctionne bien
    
    Stream<Integer> stream1 = Stream.of(new int[]{1,2,3,4}); 
    // Erreur de compilation, Type mismatch: cannot convert from Stream<int[]> to Stream<Integer>
    
  3. Nous pouvons utiliser la Collection stream() pour créer un flux séquentiel et parallelStream() pour créer un flux parallèle.

    List<Integer> myList = new ArrayList<>();
    for(int i=0; i<100; i++) myList.add(i);
    		
    // flux séquentiel
    Stream<Integer> sequentialStream = myList.stream();
    		
    // flux parallèle
    Stream<Integer> parallelStream = myList.parallelStream();
    
  4. Nous pouvons utiliser les méthodes Stream.generate() et Stream.iterate() pour créer un flux.

    Stream<String> stream1 = Stream.generate(() -> {return "abc";});
    Stream<String> stream2 = Stream.iterate("abc", (i) -> i);
    
  5. En utilisant les méthodes Arrays.stream() et String.chars().

    LongStream is = Arrays.stream(new long[]{1,2,3,4});
    IntStream is2 = "abc".chars();
    

Conversion d’un flux Java en Collection ou Tableau

Il existe plusieurs façons d’obtenir une Collection ou un Tableau à partir d’un flux Java.

  1. Nous pouvons utiliser la méthode collect() de Java Stream pour obtenir une liste, une carte ou un ensemble à partir d’un flux.

    Stream<Integer> intStream = Stream.of(1,2,3,4);
    List<Integer> intList = intStream.collect(Collectors.toList());
    System.out.println(intList); // affiche [1, 2, 3, 4]
    
    intStream = Stream.of(1,2,3,4); // le flux est fermé, nous devons donc le recréer
    Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));
    System.out.println(intMap); // affiche {1=11, 2=12, 3=13, 4=14}
    
  2. Nous pouvons utiliser la méthode toArray() du flux pour créer un tableau à partir du flux.

    Stream<Integer> intStream = Stream.of(1,2,3,4);
    Integer[] intArray = intStream.toArray(Integer[]::new);
    System.out.println(Arrays.toString(intArray)); // affiche [1, 2, 3, 4]
    

Opérations intermédiaires de Java Stream

Jetons un coup d’œil à un exemple d’opérations intermédiaires de flux Java couramment utilisées.

  1. Exemple de filtre de flux: Nous pouvons utiliser la méthode filter() pour tester les éléments du flux selon une condition et générer une liste filtrée.

    List<Integer> myList = new ArrayList<>();
    for(int i=0; i<100; i++) myList.add(i);
    Stream<Integer> sequentialStream = myList.stream();
    
    Stream<Integer> highNums = sequentialStream.filter(p -> p > 90); // filtre les nombres supérieurs à 90
    System.out.print("Nombres élevés supérieurs à 90=");
    highNums.forEach(p -> System.out.print(p+" "));
    // imprime "Nombres élevés supérieurs à 90=91 92 93 94 95 96 97 98 99 "
    
  2. Exemple de map de flux: Nous pouvons utiliser map() pour appliquer des fonctions à un flux. Voyons comment l’utiliser pour appliquer une fonction majuscule à une liste de chaînes.

    Stream<String> names = Stream.of("aBc", "d", "ef");
    System.out.println(names.map(s -> {
    		return s.toUpperCase();
    	}).collect(Collectors.toList()));
    // imprime [ABC, D, EF]
    
  3. Exemple de tri avec sorted(): Nous pouvons utiliser sorted() pour trier les éléments du flux en passant un argument Comparator.

    Stream<String> names2 = Stream.of("aBc", "d", "ef", "123456");
    List<String> reverseSorted = names2.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    System.out.println(reverseSorted); // [ef, d, aBc, 123456]
    
    Stream<String> names3 = Stream.of("aBc", "d", "ef", "123456");
    List<String> naturalSorted = names3.sorted().collect(Collectors.toList());
    System.out.println(naturalSorted); //[123456, aBc, d, ef]
    
  4. Exemple de flatMap(): Nous pouvons utiliser flatMap() pour créer un flux à partir du flux de listes. Voyons un exemple simple pour dissiper ce doute.

    Stream<List<String>> namesOriginalList = Stream.of(
    	Arrays.asList("Pankaj"), 
    	Arrays.asList("David", "Lisa"),
    	Arrays.asList("Amit"));
    // Applatissez le flux de List<String> en flux de chaînes
    Stream<String> flatStream = namesOriginalList
    	.flatMap(strList -> strList.stream());
    
    flatStream.forEach(System.out::println);
    

Opérations terminales de flux Java

Jetons un coup d’œil à quelques exemples d’opérations terminales de flux Java.

  1. Exemple de réduction de flux: Nous pouvons utiliser reduce() pour effectuer une réduction sur les éléments du flux, en utilisant une fonction d’accumulation associative, et renvoyer un Optional. Voyons comment nous pouvons l’utiliser pour multiplier les entiers dans un flux.

    Stream<Integer> nombres = Stream.of(1,2,3,4,5);
    		
    Optional<Integer> intOptionnel = nombres.reduce((i,j) -> {return i*j;});
    if(intOptionnel.isPresent()) System.out.println("Multiplication = "+intOptionnel.get()); //120
    
  2. Exemple de comptage de flux: Nous pouvons utiliser cette opération terminale pour compter le nombre d’éléments dans le flux.

    Stream<Integer> nombres1 = Stream.of(1,2,3,4,5);
    		
    System.out.println("Nombre d'éléments dans le flux = "+nombres1.count()); //5
    
  3. Exemple de forEach() de Stream: Cela peut être utilisé pour itérer sur le flux. Nous pouvons l’utiliser à la place de l’itérateur. Voyons comment l’utiliser pour imprimer tous les éléments du flux.

    Stream<Integer> nombres2 = Stream.of(1,2,3,4,5);
    nombres2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
    
  4. Exemples de match() de Stream: Voyons quelques exemples des méthodes de correspondance dans l’API Stream.

    Stream<Integer> nombres3 = Stream.of(1,2,3,4,5);
    System.out.println("Le flux contient-il 4 ? "+nombres3.anyMatch(i -> i==4));
    //Le flux contient-il 4 ? true
    
    Stream<Integer> nombres4 = Stream.of(1,2,3,4,5);
    System.out.println("Le flux contient-il tous les éléments inférieurs à 10 ? "+nombres4.allMatch(i -> i<10));
    //Le flux contient-il tous les éléments inférieurs à 10 ? true
    
    Stream<Integer> nombres5 = Stream.of(1,2,3,4,5);
    System.out.println("Le flux ne contient-il pas 10 ? "+nombres5.noneMatch(i -> i==10));
    //Le flux ne contient-il pas 10 ? true
    
  5. Exemple de findFirst() de Stream: Il s’agit d’une opération terminale de court-circuit, voyons comment nous pouvons l’utiliser pour trouver la première chaîne d’un flux commençant par D.

    Stream<String> names4 = Stream.of("Pankaj","Amit","David", "Lisa");
    Optional<String> firstNameWithD = names4.filter(i -> i.startsWith("D")).findFirst();
    if(firstNameWithD.isPresent()){
    	System.out.println("Premier prénom commençant par D="+firstNameWithD.get()); //David
    }
    

Limitations de l’API Stream de Java 8

L’API Stream de Java 8 apporte beaucoup de nouveautés pour travailler avec des listes et des tableaux, mais elle a aussi quelques limitations.

  1. Expressions lambda sans état: Si vous utilisez un flux parallèle et que les expressions lambda ont un état, cela peut entraîner des réponses aléatoires. Regardons cela avec un programme simple. StatefulParallelStream.java

    package com.journaldev.java8.stream;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class StatefulParallelStream {
    
    	public static void main(String[] args) {
    
    		List<Integer> ss = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
    		List<Integer> result = new ArrayList<Integer>();
    		 
    		Stream<Integer> stream = ss.parallelStream();
    		 
    		stream.map(s -> {
    		        synchronized (result) {
    		          if (result.size() < 10) {
    		            result.add(s);
    		          }
    		        }
    				return s;
    		    }).forEach( e -> {});
    		 System.out.println(result);   
    	}
    }
    

    Si nous exécutons le programme ci-dessus, vous obtiendrez des résultats différents car cela dépend de la manière dont le flux est itéré et nous n’avons pas d’ordre défini pour le traitement parallèle. Si nous utilisons un flux séquentiel, ce problème ne se posera pas.

  2. Une fois qu’un flux est consommé, il ne peut plus être utilisé ultérieurement. Comme vous pouvez le voir dans les exemples ci-dessus, à chaque fois que je crée un flux.

  3. Il y a beaucoup de méthodes dans l’API Stream et la partie la plus confuse concerne les méthodes surchargées. Cela rend la courbe d’apprentissage plus longue.

C’est tout pour le tutoriel sur l’exemple de flux Java 8. J’ai hâte d’utiliser cette fonctionnalité et de rendre le code lisible avec de meilleures performances grâce au traitement parallèle. Référence : Java Stream API Doc

Source:
https://www.digitalocean.com/community/tutorials/java-8-stream