La principale nouveauté de Java 8 est l'API Stream. Elle permet de mettre en œuvre une approche de la programmation fonctionnelle en parcourant, filtrant et modifiant des données de façon séquentielle ou parallèle.
Elle se trouve dans le package java.util.stream. Plusieurs classes et interfaces fournissent une instance de cette API, comme la classe Arrays, Files ou les classes étendant l'interface Collection comme ArrayList par exemple.
Plusieurs méthodes existent pour créer des Streams, c'est ce que nous allons aborder dans cet article.
Il est possible de créer un Stream vide afin d'éviter des NPE ou de retourner null et complexifier le développement.
Stream<String> myStream = Stream.empty();
Exemple de cas d'utilisation :
private Stream<String> personsToStream(List<String> persons){
return persons == null ? Stream.empty() : persons.stream();
}
Transformation d'une collection en Stream.
Collection<String> personCollection = Arrays.asList("Alex", "Claire", "Angel");
Stream<String> myStream = personCollection.stream();
Remarque : Plusieurs classes et interfaces utilisent l'interface Collection. Ci-dessous quelques exemples :
List<String> personList = Arrays.asList("Alex", "Claire", "Angel");
Stream<String> myStream = personList.stream();
Set<String> personSet = new HashSet<>(Arrays.asList("Alex", "Claire", "Angel"));
Stream<String> myStream = personSet.stream();
Il est possible de convertir un Tableau en Stream via la class Arrays, comme suit :
String[] arrayString = new String[] {"Alex", "Claire", "Angel", null};
Stream<String> myStream = Arrays.stream(arrayString);
myStream.forEach(System.out::println);
/**
Output:
Alex
Claire
Angel
null
*/
Attention : La méthode Arrays.stream n'est pas nullSafe, par conséquent, un NPE peut être levé si arrayString est null.
String[] arrayString = null;
Stream<String> myStream = Arrays.stream(arrayString);
myStream.forEach(System.out::println);
/**
Output en erreur:
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Arrays.stream(Arrays.java:5427)
at tools.Main.main(Main.java:11)
*/
On peut éviter ce problème en utilisant la classe Optional (détaillé dans cet article), ou la méthode Stream.ofNullable, disponible à partir de java 9 (Cf. chapitre Stream.ofNullable).
// Java 8 alternative avec Optional
String[] arrayString = null;
Stream<String> myStream = Optional.ofNullable(arrayString).map(Arrays::stream).orElseGet(Stream::empty);
myStream.forEach(System.out::println);
// Java 9
String[] arrayString = null;
Stream<String> myStream = Stream.ofNullable(arrayString).flatMap(Arrays::stream);
myStream.forEach(System.out::println);
// Java 9 Alternative avec Optional
String[] arrayString = null;
Stream<String> myStream = Optional.ofNullable(arrayString).stream().flatMap(Arrays::stream);
myStream.forEach(System.out::println);
Utilisation du flatMap pour "aplatir" la liste à "streammer", passant donc de Stream<List<String>> à Stream<String>.
Fournir les éléments directement en paramètre de la méthode statique of est une alternative.
Stream<String> myStream = Stream.of("Alex", "Claire", "Angel", null);
Attention : Même si la méthode of admet 1 ou plusieurs paramètres, si nous l'utilisons avec "un seul paramètre à null", un NPE sera levé, cependant, si plusieurs paramètres sont fournis, alors les valeurs nulles sont acceptées.
Exemple, le code suivant renvoie une erreur :
Stream<String> myStream = Stream.of(null);
/*
Output en erreur :
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Arrays.stream(Arrays.java:5427)
at java.base/java.util.stream.Stream.of(Stream.java:1188)
at tools.Main.main(Main.java:9)
*/
Pour éviter ce type de problème, la méthode ofNullable a été introduit à partir de Java 9.
// Java9
List<String> asList = Arrays.asList("Alex", "Claire", "Angel", null);
Stream<String> myStream = Stream.ofNullable(asList).flatMap(List::stream);
myStream.forEach(System.out::println);
/**
Output :
Alex
Claire
Angel
null
*/
// Java9
List<String> asList = null;
Stream<String> myStream = Stream.ofNullable(asList).flatMap(List::stream);
myStream.forEach(System.out::println);
// Aucun output, mais aucune erreur
Remarque : Utilisation du flatMap pour "aplatir" la liste à "streammer", passant donc de Stream<List<String>> à Stream<String>.
Permet l'ajout d'élément en vue de la préparation d'un Stream (l'ordre d'ajout des éléments est conservé)
Stream<String> myStream = Stream.<String>builder().add("Alex").add("Claire").add("Angel").add(null).build();
Le Stream est construit lors de l'appel à la méthode build.
La méthode generate permet de créer un stream ordonné infini.
Dans l'exemple suivant, nous créons un Stream "infini" avec la méthode generate. Puis nous fixons la taille du Stream à 3 pour éviter une boucle infinie (via la méthode limit). Puis, Affichage du contenu.
Stream<Long> myStream = Stream.<Long>generate(() -> new Random().nextLong()).limit(3);
myStream.forEach(System.out::println);
/**
Output:
-5386638481609337939
6789016581126790338
-5191696577994832266
*/
Remarque : Le Stream aurait pu être écrit de la manière suivante, qui est plus courte et plus concise en utilisant les référence des méthodes :
Stream<Long> myStream = Stream.<Long>generate(new Random()::nextLong).limit(3);
Une autre façon de générer un stream infini est d'utiliser la méthode iterate. En premier paramètre la valeur initiale, en second une fonction qui sera appliquée à chaque tour pour obtenir la valeur de l'élément courant.
Le paramètre fourni en entrée de cette fonction est le résultat de cette dernière au précédent tour (valeur initial si premier tour).
Par conséquent, il est facile de réaliser un compteur. Ici, on initialise à 0, puis on incrément de 1 en 1. On ajoute une limite afin d'éviter une boucle infinie (via la méthode limit).
Stream<Integer> myStream = Stream.<Integer>iterate(0, x -> x + 1).limit(3);
myStream.forEach(System.out::println);
/**
Output:
0
1
2
*/
Les Streams primitifs, sont des Streams typés (int, long, double). Pour ces types, on utilisera alors les classes IntStream, longStream et DoubleStream, qui peuvent être plus facile à manipuler que les Stream de base (Stream<T>), car ils embarquent plusieurs outils avec eux (exemple : récupération de la valeur maximale du stream).
Ces classes facilitent également la création de Stream contenant une liste d'entier incrémentée. Cela est réalisé via les méthodes range (début, fin exclus) et rangeClosed(début, fin inclus) :
IntStream myStreamInt = IntStream.range(1, 4);
myStreamInt.forEach(System.out::println);
LongStream myStreamLong = LongStream.range(1, 4);
myStreamLong.forEach(System.out::println);
/** Output :
1
2
3
1
2
3
*/
IntStream myStreamInt = IntStream.rangeClosed(1, 4);
myStreamInt.forEach(System.out::println);
LongStream myStreamLong = LongStream.rangeClosed(1, 4);
myStreamLong.forEach(System.out::println);
/** Output :
1
2
3
4
1
2
3
4
*/
A noter que pour la classe DoubleStream, la génération d'un stream peut être réalisée via la classe Random, comme suit :
Random random = new Random();
DoubleStream myStreamDouble = random.doubles(4);
myStreamDouble.forEach(System.out::println);
/**
Output:
0.027171859265936016
0.6275420062558492
0.5934360221641584
0.9976738218178213
*/
Il est possible de passer d'un Stream<T> à un Stream primitif à tout moment via la méthode mapToInt, mapToLong etc et inversement via mapToObj.
Rappel : Un type primitif ne peut être null. Par conséquent, si lors d'un mapping un null est transmis au Stream, un NPE sera levé.
LauLem.com - Conditions Générales d'Utilisation - Informations Légales - Charte relative aux cookies - Charte sur la protection des données personnelles - A propos