AXOPEN

JAVA 8 – API Stream – Introduction sur des collections

Introduction à l’utilisation des STREAM

Java 8 arrive avec une toute nouvelle API Stream qui utilise les Lambda. Cette nouvelle API offre la possibilité de simplifier l’écriture, d’améliorer la performance ainsi d’augmenter la lisibilité d’un certain nombre de code. Nous allons essayer ici de voir comment les utiliser et dans de voir dans quels cas l’utilisation de cette API est utile. Dans un second article, nous nous interresserons aux performances des stream et mode « normal » et en mode « parallel ».

Ici, nous nous concentrerons que sur des streams créés sur des collections et en particulier depuis une liste.

Un stream peut se créer sur n’importe quel objet de type Collections, par exemple une liste (List). Ici nous allons partir d’un exemple ou nous avons une liste de Commande. Une commande ayant un numéro de commande ainsi qu’un montant. C’est un exemple classique que nous rencontrons tous les jours dans le développements d’applications WEB. Voici notre gentille classe Commande.

public class Commande {
    String numero;
    double montant;
    public String getNumero() {
        return numero;
    }
    public void setNumero(String numero) {
        this.numero = numero;
    }
    public double getMontant() {
        return montant;
    }
    public void setMontant(double montant) {
        this.montant = montant;
    }
    public Commande(String numero, double montant) {
        super();
        this.numero = numero;
        this.montant = montant;
    }
}

Nous allons commencer par un exemple très simple qui va nous permettre de trier une liste de commande en ne selectionnant que les commandes du mois de mai 2014. D’abord, créons une liste de commande avec des dates et des montants différents.

List listeCommandes = new ArrayList();
 listeCommandes.add(new Commande("20140509", 113.12));
 listeCommandes.add(new Commande("20140508", 113.07));
 listeCommandes.add(new Commande("20140507", 356.03));
 listeCommandes.add(new Commande("20140512", 78.94));
 listeCommandes.add(new Commande("20140409", 163.23));
 listeCommandes.add(new Commande("20140429", 982.34));
 listeCommandes.add(new Commande("20140508", 172.89));

Pour simplifier l’opération, chaque commande aura comme numéro la date (en string) de quand la commande a été enregistré.

L’utilisation de FILTER

Avant JAVA 8, pour faire ceci, il faut écrire quelque chose comme:

List lListeCommandeMoisMai =new ArrayList();
for (Commande commande : listeCommandes) {
  if(commande.numero.startsWith("201405")){
    lListeCommandeMoisMai.add(commande);
  }
}

Avec JAVA 8 et l’utilisation des stream et lambda, il suffit d’écrire:

List lListeCommandeMoisMai = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405"))
 .collect(Collectors.toList());

Pour afficher le résultat:

for (Commande commande : lListeCommandeMoisMai) {
 System.out.println(commande.numero);
}

Voici, donc notre premier stream, qui a permis de trier notre liste de commande en ne prenant en compte que les commandes de mai « 201405 ». On se rend compte aisément que l’écriture de ce même traitement avec des streams et l’utilisation des lambda simplifie très clairement la lecture et nul besoin d’utiliser une boucle sur les commandes afin de réaliser notre filtre. L’expression du filtre x.numero.startsWith() est très lisible même pour quelqu’un qui ne connait pas JAVA 8.

Nous allons voir maintenant toutes les autres instructions de base qui sont possible sur des stream().

L’utilisation de MAP sur les STREAM

L’utilisation de l’instruction map() permet de choisir quel élement on veut « récupérer » dans notre steam. Par exemple, supposons que nous souhaitons récupérer les montants des commandes du mois de mai.

List listeCommandeMai = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405")).map(x -> x.montant)
 .collect(Collectors.toList());

Avec l’utilisation de map, nous avons choisi de récupérer que le montant à retourner dans la listeCommandeMai qui devient une liste de Double.

Mais l’utilisation de map permet aussi de modifier directement ce que nous allons récupérer! Par exemple, imaginons que nous voulions calculer la TVA sur ce montant. Nous pouvons écrire:

List listeTVA = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405")).map(x -> x.montant*0.2)
 .collect(Collectors.toList());

Ainsi, en une opération, nous avons et filter et récupérer la TVA sur toutes les commandes, le tout en très peu de ligne de codes!

JAVADOC: Returns a stream consisting of the results of applying the given function to the elements of this stream.

L’opération COLLECT sur les STREAM

En fonction de ce que vous souhaiter récupérer, il est directement possible d’appeler sur votre STREAM la méthode COLLECT (opération terminale). Ici nous l’utilisons pour récupérer nos éléménts dans une liste.

JAVADOC: Performs a mutable reduction operation on the elements of this stream using a Collector.

L’opération GET sur les STREAM

L’opération GET permet de ne récupérer qu’un seul élément et est utilisée pour toutes les opérations sur les STREAM qui sont amenées à ne récupérer qu’un élément. Par exemple, MAX, MIN…

L’utilisation de l’instruction SORTED et DISTINCT sur les STREAM

Les streams offrent aussi la possibilité de trier directement le résultat de sorti, par exemple si nous voulions récupérer les TVA par ordre croissant, il suffit d’écrire: Ici l’order naturel est utilisé car aucun comparator n’est passé en paramètre de la méthode sorted.

List lListeCommandeMai = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405")).map(x -> x.montant*0.2)
 .sorted()
 .collect(Collectors.toList());

Mais il est aussi possible de ne choisir que des éléments distinct avec l’instruction distinct(). Ne même l’ordre naturel est utilisé pour réaliser le disctinct.

List lListeCommandeMai = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405")).map(x -> x.montant*0.2)
 .distinct()
 .collect(Collectors.toList());

Quant on souhaite trié sur un ordre non naturel, il est possible d’utiliser son propre comparator.

L’utilisation de l’instruction SORTED ou DISTINCT avec un comparator:

List lListeCommandeMai= listeCommandes.stream()
.filter(x -> x.numero.startsWith("201405"))
.sorted((x1, x2) -> (int)(x1.montant - x2.montant))
.collect(Collectors.toList());

JAVADOC Sorted: Returns a stream consisting of the elements of this stream, sorted according to natural order. 

JAVADOC Distinct: Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream.

L’utilisation de l’instruction MAX ou MIN sur les STREAM

Très simplement, pour récupérer le max des commandes, l’utilisation des STREAM simplifie encore l’écriture. Ici nous récupérer la commande avec le plus gros montant en spécifiant nous même notre comparator. Attention, ici, on remarque que l’on utilise le méthode GET pour récupérer le résultat car max par définition ne renvoie qu’un élément.

Commande commande = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405"))
 .max((x1, x2) -> (int) (x1.montant - x2.montant)).get();

De la même manière nous pouvons utiliser la méthode min pour récupérer le plus petit élément.

JAVADOC MAX: Returns the maximum element of this stream according to the provided Comparator.

JAVADOC MIN: Returns the minimum element of this stream according to the provided Comparator.

L’utilisation de la commande LIMIT sur les STREAM

Il est aussi possible de limiter le nombre de résultat en sortie d’un stream avec l’utilisation de la commande limit(). Ici nous allons récupérer les deux commandes avec le plus gros montant.

List lListeCommandeMai = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405"))
 .sorted((x1, x2) -> (int)(x1.montant - x2.montant))
 .limit(2)
 .collect(Collectors.toList());

JAVADOC LIMIT: Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length.

L’utilisation de la methode COUNT sur les STREAM

Il est aussi parfois interessant de juster compter le nombre d’éléments présent dans notre filtre. Pour ce faire, on peut utiliser la méthode COUNT directement sur le stream

long nombreElement = listeCommandes.stream()
 .filter(x -> x.numero.startsWith("201405"))
 .count();

JAVADOC COUNT: Returns the count of elements in this stream.

Source: http://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

Voilà une courte introduction sur l’API STREAM JAVA 8. Donc un article suivant nous verrons quels sont les impacts sur les performances de l’utilation des streams. En particulier le rôle des collections et la comparaison entre l’utilisation de stream et parallelStream.