Thématiques principales

Affichage des articles dont le libellé est Java 8. Afficher tous les articles
Affichage des articles dont le libellé est Java 8. Afficher tous les articles

dimanche 3 mars 2019

OSGI : Les annotations

Nous avons vu comment déclarer un Activator/Factory/Tracker [ex-karaf], [uttu-felix] pour construire des bundles OSGI, ensuite nous avons vu comment tirer parti de la philosophie Spring pour alléger la définition des beans, des services  et leur utilisations avec SpringDM et Blueprint [bp-karaf], [uttu-bp]; dans ce dernier article (où presque sur OSGI), je vous propose de nous intéresser à une dernière façon de faire des bundles : en utilisant des annotations [scr-annot].

Bon la question ici n’est pas de faire un laïus sur les annotations, j’en ai déjà parlé dans un article [uttu-annot], donc on va aller directement dans le vif du sujet parce que l’on se doute bien que pour remplacer nos Activator and coe et/ou les description xml, il va nous falloir quelques concepts de base.

Les annotations

Alors en fait si vous ne l’aviez pas encore vraiment compris, avec OSGI, finalement il n’y à pas forcément beaucoup de concepts à mettre en oeuvre…. en effet finalement, on à des beans qui implémentent des services et qui vont être injecté dans d’autres beans…

Du coup dans notre contexte, on va rencontrer les annotations suivantes:
  • @Component: permet de déclarer un bean
  • @Activate/@Deactivate: permet de spécifier une méthode gérant une initialisation spécifique (où inversement pour l’annotation @Deactivate)
  • @Référence: permet de spécifier une donnée membre dans laquelle devra être injectée un autre bean (spécifié par @Component)

samedi 2 mars 2019

OSGI : SpringDM et BluePrint

Nous revoilà avec OSGI. Nous parlions à la fin de l’article sur Karaf et Service-mix de SpringDM et BluePrint… mais qu’est ce que c’est?

SpringDM et Blueprint [gemini] sont des frameworks Java et XML permettant la simplification de la description et la gestion des services OSGI de nos Bundles.

SpringDM a été le précurseur et a été suivi par Blueprint grâce à une normalisation (plus une adhésion) au sein de la norme OSGI.

Le principe est d’utiliser les mécanismes de Spring pour réaliser la déclaration de Beans au sein des bundles qui seront chargé par un bundle context dédiés. Cela ne va pas nous empêcher de devoir déclarer nos services sous la forme d’interfaces et d’en réaliser des implémentation mais cela va nous permet de ne plus avoir à implémenter toute la partie Activator, Factory et Tracker que nous avions vu dans les précédents articles sur le sujet…

Quoi ? mais du coup il n’y à plus rien dans le Bundle?

mercredi 27 février 2019

OSGI : Karaf ServiceMix

On est presque à la fin du mois et je n’ai écrit qu’un seul article! Heureusement j’ai un sujet au chaud pour combler le vide.

On avait parlé du framework OSGI Felix [osgi-felix] la dernière fois et nous avions vu dans un précédent article qu’il en existe d’autres packages sous la forme de serveur d’application (comme le serait un Jboss [jboss] ou un Weblogic [weblogic]).

Nous allons justement nous intéresser à deux de ses serveurs:
  • Karaf [karaf]
  • ServiceMix [servicemix]

Bien sur ceux les connaissant diront que c’est grosso merdo la même chose, et effectivement, ServiceMix s’appuie sur Karaf mais en apportant un ensemble de bundle dédié à la communication et aux services, c’est à dire les WebServices et les services de messageries (SMTP, JMS, etc…). Il était donc important de le connaître également.

dimanche 3 février 2019

OSGI : Felix

Nous revoilà avec OSGI mais cette fois ci pour aller dans le concret, plus de blabla, maintenant on fait chauffer la JVM.

Le problème du choix

Bien tout d’abord il nous faut choisir un framework, c’est à dire une implémentation. Nous en avons vu plusieurs dans l’article précédent [osgi-fwk] et il nous faudra faire un choix. À mon sens il y avait plusieurs possibilité:

  • Knoplerfish [knoplerfish] est trop compliqué bien qu’à jour dans l'implémentation des versions OSGI
  • Karaf [karaf] est un peu trop lourd pour ce que nous voulons montrer ici (mais nous ferons un article dessus)
  • Equinox [equinox] est sympas je l’aurais choisi si je ne prévoyais pas de présenter mon framework equinox-loader [eq-loader] qui l’utilise
  • Concierge [concierge] est hors propos, on ne va pas faire de Iot
  • Donc en toute logique, on va essayer l’utilisation de OSGI avec Felix [felix]

Tout d’abord il faut télécharger la version courante: c’est ici [felix-down]

Dockerisation

Pour simplifier son utilisation, nous allons préalablement le dockeriser pour faciliter son exploitation, on construit donc un fichier Dockerfile dans un répertoire (moi je l’ai nommé felix-framework) contenant le processus de construction suivant:

FROM openjdk:8-jre-alpine

RUN wget http://mirror.ibcp.fr/pub/apache//felix/org.apache.felix.main.distribution-6.0.1.tar.gz \
&& tar zxvpf org.apache.felix.main.distribution-6.0.1.tar.gz && rm org.apache.felix.main.distribution-6.0.1.tar.gz

COPY . /felix-framework-6.0.1/
CMD cd /felix-framework-6.0.1; java -jar bin/felix.jar


lundi 31 décembre 2018

Docker - Plugin Maven

Un petit écart dans la liste des articles qui devaient être publiés prochainement en abordant le sujet du packaging de nos builds maven dans des images Docker.

On parle d'écart mais en fait pas tant que ca car nous verrons que OSGI se prête bien à une intégration dans docker. Bref ca c’est une autre histoire.

Tout le monde connaît Docker, au moins de nom. Il s’agit d’une solution de conteneurisation [docker-pub], assimilable à de la “virtualisation allégé” (ok le raccourci est rapide) permettant de construire des architectures scalables [scala] de type [microservice] (nous en reparlerons).

L’approche classique pour construire une image docker et d’utiliser la commande “docker build”, si vous êtes un habitué de l’outil, sera de construire un Dockerfile. Je vous laisse pour cela aller consulter l’article [docker-base].

Cependant cette approche en ligne de commande, bien que pratique car universelle n’est pas forcément immédiate dans le cadre d’une industrialisation comme d’un build maven qui aurait pour résultat non pas un artifact java classique [integ-java] mais une image docker prête à l’emploi pour les tests ou la production (vous le voyez poindre le concept de Déploiement Continu?).

Heureusement pour répondre à ce problème il existe deux plugins maven [compar]:
  • spotify/maven-docker-plugin [spotify], et sa nouvelle version [spotify-2] spotify/dokerfile-maven-plugin
  • fabric8io/maven-docker-plugin [fabric8io]

Spotify

Ce plugin maven est celui ci qui collera le plus avec la logique d’utilisation de docker.

En effet, ce plugin s’utilise de façon assez simple soit par configuration xml sur les éléments basiques qu’un dockerfile pourrait contenir où en spécifiant directement un répertoire où se trouvera un dockerfile.

Ainsi ce plugin va permettre de réaliser la phase de build de la commande docker mais aussi en complément la phase de push pour livrer l’image ainsi construite dans un repository comme le docker hub [DocHub].

On pourra regretter que ce plugin ne permet pas de lancer lui même des conteneurs, chose qui aurait pu être intéressant pour la réalisation de phase de tests.

Dans le cadre de cet article, comme dans celui de [compar], nous ne nous attarderons pas sur ce plugin qui finalement fait bien son taff mais ne fournira pas en dehors du cadre de l'intégration à maven beaucoup plus de fonctionnalité que de faire son Dockerfile nous même.

Fabric8io

Le plugin fabric8io sort quand à lui plus des sentier battu en ne s’appuyant pas du tout sur un dockerfile mais sur une approche plus classique dans un environnement maven. En effet, ce plugin propose d’utiliser un descriptif xml du Dockerfile [doc-fabric] ainsi que le mécanisme des assembly [assembly-maven].

A cela, il permet aussi de gérer le cycle de vie des conteneurs en activité (start/stop/kill etc...) ainsi que de publier les images sur un repository (comme le DockerHub [DocHub])

Le plus simple pour présenter le plugin est d’en faire la démonstration.

Ainsi considérons un projet java construit forcément avec un process maven. L’idée d’un build est de généralement construire jar. Dans une approche standard de packaging, le process maven appliquera une phase d’assemblage dans laquelle, le jar, sa conf et ses éventuellement dépendances seront mis dans un targz, un paquet debian où tout autre format de livraison.

Avec docker, le concept permettant d'exécuter un conteneur à partir d’une image quelque soit la plateforme permet d’imaginer une rationalisation du moyen de livraison et d'exécution.

Ainsi, parce que nous utilisons maven, nous allons enrichir un pom parent avec une configuration pluginManagement du plugin fabric8io. L’idée est alors de permettre à tout pom héritant de celui-ci de construire une image docker de packaging et d'exécution.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<profiles>
    <profile>
        <id>DOCKER</id>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>io.fabric8</groupId>
                        <artifactId>docker-maven-plugin</artifactId>
                        <version>0.27.2</version>
                        <configuration>
                            <images>
                                <image>
                                    <name>${docker.login}/${project.artifactId}:%l</name>
                                    <alias>${project.artifactId}</alias>
                                    <build>
                                        <maintainer>Collonville Thomas collonville.thomas@gmail.com</maintainer>
                                        <!-- <dockerFile>Dockerfile</dockerFile> -->
                                        <assembly>
                                            <mode>dir</mode>
                                            <permissions>auto</permissions>
                                            <targetDir>/</targetDir>
                                            <descriptor>../assembly/assembly-docker.xml</descriptor>
                                        </assembly>
                                    </build>
                                </image>
                            </images>
                        </configuration>
                        <executions>
                            <execution>
                                <id>start</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>build</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </profile>
    <profile>
        <id>PUSH</id>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>io.fabric8</groupId>
                        <artifactId>docker-maven-plugin</artifactId>
                        <version>0.27.2</version>
                        <executions>
                            <execution>
                                <id>start</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>build</goal>
                                    <goal>push</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </profile>
</profiles>

Ainsi la pré configuration maven du plugin permet de façon générale de donner un nom à l’image et spécifier ses informations générales (nom, mainteneur) dans la section “image”.

Ensuite dans une section build on va préciser le contenu de l’image en utilisant les mécanismes d’assembly maven que l’on devra préciser dans le fichier “assembly/assembly-docker.xml”

Enfin en coherence avec la logique des plugins maven se constituant d’une partie configuration et d’une partie exécution, dans cette dernière, on va préciser quelle commande docker nous allons appliquer.

Ici on notera qu’il y a deux profiles maven, l’un DOCKER de base qui apporte la configuration et la construction de l’image et un second PUSH dans lequel on appliquera la commande “push” de docker (cela nécessitera l’utilisation de la propriété docker.login précisant le compte du DockerHub associé et présent dans le nom de l’image)

L’utilisation de ce pom parent passe ensuite par la surcharge du profile DOCKER dans une section plugin (le profile PUSH étant directement hérité).

La surcharge est rapide bien qu’un peu verbeuse à cause du profile maven. (à noter que dans un process complet et générique on pourra in fine s’en passer)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<profiles>
    <profile>
        <id>DOCKER</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>io.fabric8</groupId>
                    <artifactId>docker-maven-plugin</artifactId>
                    <configuration>
                        <images>
                            <image>
                                <build>
                                    <from>openjdk:8-jre-alpine</from>
                                    <cmd>/opt/equinox-loader/docker.sh</cmd>
                                    <ports>
                                        <port>${jmx.port}</port>
                                    </ports>
                                </build>
                            </image>
                        </images>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Dans ce pom, il s’agira juste de completer les parties manquantes c’est à dire:

  • l’image source utilisé
  • une commande de lancement
  • les ports et les volumes à déclarer
  • pour finir le contenu de l’assembly, faisant office de commande ADD et COPY d’un Dockerfile

Ici nous utiliserons l’image openjdk:8-jre-alpine comme image source que nous allons configurer le port JMX de la JVM et que nous lancerons l’application via un script sh.

Pour ce qui concerne l’assembly, il s’agit à ce niveau d’une assembly standard utilisant des section “dependencySets” et “fileSets”. Je vous invite à consulter la documentation du plugin [plugass] et/ou de lire l’article [assem].

Voilà avec ce plugin, du coup il est possible de construire des images docker contenant directement notre build prêt à l’emploi nous facilitant d’une part la diffusion du livrable mais aussi l’exploitation dans des tests et surtout l’exploitation en production.

Références

[docker-pub] https://un-est-tout-et-tout-est-un.blogspot.com/2017/10/docker-publier.html
[docker-base] https://un-est-tout-et-tout-est-un.blogspot.com/2017/09/docker-construire-son-image.html
[scala] https://fr.wikipedia.org/wiki/Scalability
[integ-java] https://runnable.com/docker/java/dockerize-your-java-application
[microservice] https://fr.wikipedia.org/wiki/Microservices
[spotify] https://github.com/spotify/docker-maven-plugin
[spotify-2] https://github.com/spotify/dockerfile-maven
[fabric8io] https://github.com/fabric8io/docker-maven-plugin
[doc-fabric] http://dmp.fabric8.io/
[compar] https://dzone.com/articles/meet-the-docker-maven-plugin
[assembly-maven] http://maven.apache.org/plugins/maven-assembly-plugin/
[DocHub] https://hub.docker.com/
[assem] https://un-est-tout-et-tout-est-un.blogspot.com/2018/03/maven-assembly-targz-et-jdeb.html
[plugass] https://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html

dimanche 9 septembre 2018

IA : Neuromimétique, la régression linéaire

Nous revoilà sur un sujet un peu plus sympa pour ce début septembre, la régression linéaire.

Cet article aurait du être publié en août mais le temps a manqué, et j'ai voulu profiter du précédent article sur les Streams [1] pour essayer une approche un peu différente de ce tour d'horizon de ce que l'on peut faire avec un neurone!

Du coup aujourd'hui, il s'agit de présenter la régression linéaire en s'appuyant sur les Streams Java. Comme nous l'avions vu dans les articles précédents sur l'IA [2], nous allons créer un jeu de données d'apprentissage et un jeu de donnée de test sur deux cas d'utilisations (en fait 3 nous verrons pourquoi):
  • l'un sur l'identification de paramètres : nous allons donc demander a notre neurone de nous donner les paramètres (a,b) d'une droite tel que y=a.x+b
  • l'un sur la construction d'un modèle prédictif partant d'une droite bruité artificiellement (nous traiterons ici de plusieurs cas différents afin de considérer les situations qui marchent bien de celles, moins pertinentes)
Pour cela, nous n’utiliserons qu'un seul modèle de fonction d’activation : le linéaire [3]. Nous pourrions utiliser le sigmoïde mais ça risque de faire vraiment long... peut être que dans un autre article nous nous intéresserons a cette dernière.

Enfin concernant le mode d'apprentissage, nous utiliserons un apprentissage supervisé. Nous n'avions pas beaucoup parlé de ce point dans nos précédents articles sur la classification [2] [4], ou nous avions élaboré un modèle basé sur une correction simple du vecteur des paramétrés portés par les dendrites du neurone. Ici ça sera l'occasion de découvrir la descente de gradient [5] et par la même occasion quelques fonctions de coût nous permettant d’évaluer l'efficacité de notre modèle.

Pour bien comprendre cet article, je vais d'abord en donner les lignes directrices:

  1. Tout d'abord, comme nous n'utilisons pas encore de frameworks spécifiques pour le machine learning (comme scikit-learn [6]) ou de framework de réseau de neurone comme Tensor-Flow [7] ou Keras [8]), il nous faut d'abord construire un  neurone, lui donner une implémentation pour la fonction d'activation, et lui donner une fonction d'apprentissage.
  2. Ensuite, nous tacherons de monitorer le neurone lors de son fonctionnement afin de nous permettre d'observer comment vont évoluer ses paramètres lors de l'apprentissage, et comment va évoluer son  efficacité en fonction de ses paramètres.
  3. Enfin, nous ferons une petite séance de visualisation de données sous python afin d'avoir des schémas explicatifs des données monitorées.

Voila, ça va être dense!

Le neurone

Nous en avions déjà fait un en python pour la classification. Ici on va aller a l'essentiel:

  • Un tableau de paramètre représentant les poids des dendrites (biais inclus).
  • Une fonction linearInfer permettant le calcul de la sortie du neurone (produit matricielle du vecteur d'entrée avec le vecteur des poids) inféré sur la fonction d'activation linéaire).
  • Une fonction d'apprentissage learnStep permettant la correction des poids selon des données étiquetés fourni par la classe Data
  • Quelques méthodes utilitaires comme la méthode reset permettant d'initialiser les données des poids de façon aléatoire.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Neurone {

    public Double[] dendrites;

    public Neurone(int inSize)
    {
        this.reset(inSize);
        System.out.println(this);
    }

    public void setParameter(Double v,Double b)
    {
        this.dendrites[0]=v;
        this.dendrites[1]=b;
    }

    public void reset(int inSize)
    {
        this.dendrites= Stream.generate(() -> Math.random()/1000).limit(inSize+1).toArray(Double[]::new);
    }

    public Double linearInfer(Double[] stepInputs)
    {
        ...      
    }

    public void learnStep(Set<Data> datasSet)
    {
        ...
    }

}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Data {

    public Double[] input;
    public Double output;


    public Data(Double[] input,Double output)
    {
        this.input=input;
        this.output=output;

    }
}

Nous voila donc une ébauche de neurone et une structure de données pour gérer les données d'apprentissage.


mardi 28 août 2018

Les Streams avec Java 8

De la plomberie?

Je vous avais dit que nous reviendrions sur les Streams définis dans l'API Java 8. J'avais évoqué cette API en novembre dernier lorsque nous avions fait un tour d'horizon de Java 8 [1]. Je sais ça fait un peu loin et pas mal d'eau a coulé sous les ponts, cependant je pense que même si cet article arrive un peu tard (même si nous avons quand même vu pas mal d'autres choses depuis à la place) cela ne fera pas de mal de regarder un peu plus dans le détail cette API qui est incontournable.

Tout d'abord il faut dire que le Stream est un pattern de conception, nous ne l’avions pas traité dans le blog, il serait intéressant de le traité mais il s'agit essentiellement d'un paradigme de modélisation de la gestion de flux qu'une manière d’implémenter ces dits flux. Dans ce paradigme, tout comme dans l'API [2], bien sur nous y retrouverons des traits comme la capacité de construire des pipelines de traitement.



En fait du coup, un stream, c'est quoi? un tuyau! ou plusieurs mis bout a bout entre lesquels divers filtres, transformations et traitement sont appliqués. Les éléments ainsi introduit dans le stream va alors subir les différents traitements successivement (ce n'est pas aussi simple car le stream peut avoir a traiter plusieurs éléments en même temps, voir tous les éléments) et ce tant qu'il y a des éléments. Grace a cette propriété (le tant que) les streams sont capables de traiter des listes ou des ensembles d’éléments de taille non connu (pour ne pas dire infini qui n'aurait pas beaucoup de sens...) initialement.

Dans Java 8, les streams vont donc être un complément au pattern Iterator qui se cache derrière nos structures foreach. D'une part en permettant des traitements potentiellement infini mais aussi de façon a simplifier les boucles et les traitements. En effet le constat est que les manipulations sur les listes dans les boucles sont généralement des traitements produisant des résultats sans liens avec un besoin quelconque de modifier la liste elle-même (chose généralement à bannir sans risque d'effets de bords désagréables, d'ou l'utilisation de liste immutable).

Initialisation d'un stream

Ainsi, grâce aux streams, le traitement sur un ensemble d’éléments (liste, ensemble, infini ou non) peut être infini si le stream est construit comme tel, et cela ne pose aucun problème. Par exemple on peut créer des streams finis voir vide:

1
2
3
4
5
6
7
8
9
Stream emptyStream = Stream.empty();

Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOFromArray = collection.stream();

String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOFromArray2 = Arrays.stream(arr);

IntStream streamOfChars = "abc".chars();

ou des streams infinis (vous verrons comment les limiter), en compréhension

1
2
3
Stream<String> genStream = Stream.generate(() -> "1");
IntStream s=IntStream.range(1,10);
Stream<String> iterateStream = Stream.iterate("thomas",(x) -> x.toUpperCase());

Au passage on constate que les Streams s'appuient aussi beaucoup sur les nouvelles API fonctionnelles apportées aussi par Java 8 avec les lambdas expression (sujet traité dans [1]).

On peut aussi construire des streams statiquement avec des builders comme on le ferait avec un StringBuilder (pour ceux qui connaissent)

1
Stream<String> phrase=Stream.<String>builder().add("Ceci ").add("est ").add("une ").add("phrase.").build();

Ou a partir de chaîne de caractères pour en faciliter le parsing ou la dissection avec des RegEx ( StreamSupport)

Filtration

Maintenant que l'on a des streams sous la main, le tout est d'en faire quelque chose. Comme nous en avions parlé, différents traitements sont possibles et chacun d'eux nous permettra de construire alors un nouveau stream. Il importe donc de bien comprendre l'emploi de ces differentes fonctions car comme nous l'avons évoqué, ces traitements vont se cascader, impliquant que l'ordre d’exécution à une importance significative.

Par exemple :

Considérons un stream produisant des entiers dont on ne gardera que les éléments pair. On voudra ne conserver que les 20 premiers éléments du calcul. Pour répondre à ce problème, il nous faudra employer la fonction filter et la fonction limit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
System.out.println("Stream1");
Stream<Integer> stream = Stream.iterate(0,(x)-> x+1 );
stream.limit(10).filter((x) -> (x % 2) == 0).forEach(System.out::println);

System.out.println("Stream2");
Stream<Integer> stream2 = Stream.iterate(0,(x)-> x+1 );
stream2.filter((x) -> (x % 2) == 0).limit(10).forEach(System.out::println);

System.out.println("Stream3");
IntStream stream3=new Random().ints(0,100);
stream3.limit(10).filter((x) -> (x % 2) == 0).sorted().forEach(System.out::println);

Avec cet exemple simple, on voit bien que l'ordre d’exécution des fonctions a une importance. Le résultat est affiché via la fonction foreach grâce a laquelle il est possible de passer en paramètre une fonction qui sera appliqué a tous les éléments produit par le stream.

Manipulation sur les streams

A la place et comme suit, il est possible d'utiliser la fonction collect qui renvoi un élément de l'API collection et aggregant l'ensemble des éléments produits par le stream (une fois celui-ci vidé et trié avec la fonction sorted).

1
2
3
System.out.println("Stream4");
Stream<Integer> stream4=new Random().ints(0,100).boxed();// on fait un boxed pour traduire de IntStream
Set<Integer> set=stream4.filter((x) -> (x % 2) == 0).limit(10).sorted().collect(Collectors.toSet());

Voyons maintenant les fonctions un peu plus complexe que sont map et reduce. Ces deux fonctions sont un peu le cœur des streams car elles vont nous permettre d'appliquer des opérations de traitements, transformations et calculs.

Prenons par exemple le besoin de calculer un ensemble de point suivant une droite tel que y=a*x+b avec a=4 et b=3. Avec l'API Stream, cela nous donne le code suivant:

1
2
Stream<Double> stream= DoubleStream.iterate(0.00, x -> x + 1 ).boxed();
Set<Double> s=stream.map(x -> 4*(x+Math.random())+3).limit(200000).collect(Collectors.toSet());

De la même manière, il va être possible de calculer la moyenne des différents points avec la fonction reduce :

1
2
Integer value=IntStream.range(1,10).reduce((x,y)->x+y).getAsInt();
System.out.println(value); //45

Avec ces deux exemples simples, on comprend assez facilement le role des fonctions map/reduce.

La première va construire un stream constitué des résultats de la fonction passée en paramètre (un peu comme foreach sans être une fonction finale pour le stream).

A noter que des variantes de la fonction map existent : des fonctions comme mapToObject, boxed, mapToDouble,etc...permettent de faciliter la conversion d'un Stream d'un type d'objet en un Stream contenant un autre type d'objets.

La seconde, la fonction reduce, a l'inverse va appliquer de façon globale la fonction passée en paramètre afin de restituer un resultat unitaire (ici une moyenne).

Un peu plus loin

Afin afin de permettre des traitements spécifiques, il est possible d'employer des Streams spécifiques dédiés a la manipulation des entiers, des doubles, des longs etc... Ces Streams permettent alors de récupérer pas exemple des statistiques sur les données du stream comme avec l’objet IntSummaryStatistics

1
2
3
4
5
6
Stream<Integer> stream4=new Random().ints(0,100).boxed();
IntSummaryStatistics stats=stream4.mapToInt(x -> x).summaryStatistics();
System.out.println("Plus grand" + stats.getMax());
System.out.println("Plus petit" + stats.getMin());
System.out.println("La somme" + stats.getSum());
System.out.println("La moyenne" + stats.getAverage());

Voila nous sommes a la fin de la présentation des Streams. Dernier point a évoquer est la possibilité d'utiliser la fonction parallel qui s'appuie sur l'API Java 7 fork/join [3] et fourni la possibilité de paralléliser les traitements sur les éléments du stream afin d'en améliorer les performances d’exécution. Il sera peut être utile dans un article futur de nous intéresser a cette fameuse API fork/join.


1
2
Stream<Double> stream2= DoubleStream.iterate(0.00, x -> x + 1 ).boxed();
Set<Double> s2=stream2.parallel().map(x -> 4*(x+Math.random())+3).limit(200000).collect(Collectors.toSet());

Voila, nous avons fait le tour de l'API Stream de Java 8. Elle utilise largement les lambda expressions et implémente une nouvelle manière de penser le traitement des données tout en fournissant des outils simples de manipulation (map/reduce) sans omettre le besoin de performance (parallel). Pour ceux voulant d'autres exemples d'utilisation je vous invite a consulter [4], [5], [6] et [7].

Pour avoir le code source (en vrac) c'est ici [8].

Référence:

  • [1] https://un-est-tout-et-tout-est-un.blogspot.com/2017/11/evolution-java-8.html
  • [2] https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
  • [3] http://blog.paumard.org/2011/07/05/java-7-fork-join/
  • [4] https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/
  • [5] https://blog.axopen.com/2014/05/java-8-api-stream-introduction-collections/
  • [6] https://www.tutorialspoint.com/java8/java8_streams.htm
  • [7] https://www.baeldung.com/java-8-streams
  • [8] https://github.com/collonville-tom/tc-un-est-tout-et-tout-est-un/tree/master/Java8-Streams/src