Modules Java: Une application exemple – Intérêts

  • Modules Java: Présentation et concepts
  • Modules Java: JRE modulaire
  • Modules Java: Une application exemple – Conception
  • Cet article: Modules Java: Une application exemple – Intérêts
  • 1. Réalisation finale de l’application exemple

    Pour finaliser la réalisation de l’application présentée comme exemple d’utilisation des modules Java, on va créer le module principal net.mtuto.superprocess. Ce module contient le point d’entrée de l’application. Se positionner dans le dossier de travail application/. Créer, ensuite, la classe unique de ce module:
    mkdir src/net/mtuto/superprocess/
    vi src/net/mtuto/superprocess/SProcesseur.java
    
    Contenu de ce fichier:
    package net.mtuto.superprocess;
    import java.util.*;
    import net.mtuto.repartition.facade.Repartiteur;
    public class SProcesseur  
    {
        public static void main( String[] args )
        {
            System.out.print("Super processeur, que voulez-vous traiter ?: ");
            Scanner scanner = new Scanner(System.in);
            String sujet = scanner.nextLine();
            Repartiteur r = new Repartiteur();
            List<String> lr = r.traiter(sujet);
            for (String s: lr) System.out.println(s);
        }
    }
    
    Créer le descripteur de ce module principal dans src/module-info.java:
    module net.mtuto.superprocess {
      exports net.mtuto.superprocess;
      requires net.mtuto.repartition;
    }
    
    Enfin, compiler et créer le fichier JAR du module:
    javac --add-modules net.mtuto.repartition -p spmodules/ -d classes src/module-info.java src/net/mtuto/superprocess/SProcesseur.java
    jar --create --file spmodules/superprocess.jar --main-class net.mtuto.superprocess.SProcesseur -C classes/ .
    

    2. Essais d’exécution

    A ce stade, la réalisation de l’application est achevée. L’ensemble des éléments de l’application est sous forme de modules Java. Ces modules sont contenus dans des fichiers JAR qui se trouvent dans le dossier application/spmodules. Exécuter:
    ls -l spmodules/*.jar
    
    Le résultat doit être: modules java exemplePour exécuter l’application:
    java --module-path spmodules/ -m net.mtuto.superprocess
    
    Essayer plusieurs exécutions, avec à chaque fois un objet différent du traitement: 1234, 1234 + 5678, 1234 x 5678, finir et enfin: marcher. Remarquer que l’application répond bien au cahier des charges de départ.

    3. Vérification préalable de la cohérence des modules

    L’un des apports importants des modules Java est la vérification préalable à l’exécution de la cohérence des modules constituant une application. En Java 1.8-, cette vérificartion est inexistante et la JVM charge les classes exécutées au fur et à mesure de l’avancement de l’exécution et en se fiant à l’ordre définit dans le classpath. De ce fait, ce n’est qu’au cours de l’exécution que la JVM peut se rendre compte de l’absence (ou l’incohérence) d’une partie du code exécutable. Supprimons un module du dossier qui les contient et tentons de lancer l’application. Exécuter dans l’ordre:
    mkdir tmp
    mv spmodules/calcul.jar tmp/
    java --module-path spmodules/ -m net.mtuto.superprocess
    
    L’application ne s’exécute pas et on obtient un message d’erreur dès le lancement:
    Error occurred during initialization of boot layer
    java.lang.module.FindException: Module net.mtuto.calcul not found, required by net.mtuto.repartition
    
    Ce message est très claire: il manque le module net.mtuto.calcul à l’arbre de dépendance de l’application. Exécuter maintenant:
    cp -p tmp/calcul.jar spmodules/
    mv tmp/calcul.jar spmodules/calcul2.jar
    
    Le module net.mtuto.calcul est, maintenant, dupliqué. L’application ne s’exécute pas et on obtient un message d’erreur au lancement:
    Error occurred during initialization of boot layer
    java.lang.module.FindException: Two versions of module net.mtuto.calcul found in spmodules (calcul.jar and calcul2.jar)
    
    Fini le risque d’utilisation de plusieurs versions des mêmes librairies, les conflits de versions et le JAR Hell (enfin je ne suis pas tout à fait certain de ce dernier point car il est trop tôt pour en être certain mais on l’espère bien). Supprimer le fichier calcul2.jar pour pouvoir poursuivre dans de bonnes conditions.

    4. Evolution fonctionnelle facilitée

    On se propose de faire évoluer le fonctionnement de l’application de manière à rendre le résultat de calcul plus lisible. La réponse doit comporter l’opération demandée = résultat au lieu du résultat seul. Au regard de l’architecture modulaire de l’application, seul le module net.mtuto.calcul doit être modifié. De plus seule la classe de façade Calculer.java doit l’être. Tout le risque de la modification se trouve donc confiné à cette partie du code applicatif. Modifier cette classe de manière que deux lignes des méthodes addition et multiplication deviennent:
    ...
    return opAddition + " = " + addition.additionner(a,b).toString();
    ...
    return opMultiplication + " = " + multiplication.multiplier(a,b).toString();
    ...
    
    Après cela, on reconstruit le module calcul seulement.
    cd spmodules/calcul
    javac -d classes src/*.java src/net/mtuto/calcul/facade/*.java src/net/mtuto/calcul/traitement/*.java
    jar --create -M --file ../calcul.jar -C classes/ .
    
    La partie modifiée étant bien encapsulée et isolée du reste du code, on peut sans prendre de risque, ne tester que cette partie modifiée. Souvent un test unitaire automatisé suffit.

    5. Réalisation plus rapide

    Avec une architecture modulaire, les modules Java constituent des unités applicatives avec un couplage faible entre elles. Ceci favorise la réalisation de ces unités par des équipes différentes travaillant en parallèle. Cette architecture favorise également les tests unitaires automatisés du code réalisé.]]>

    Modules Java: Une application exemple – Conception

  • Modules Java: Présentation et concepts
  • Modules Java: JRE modulaire
  • Cet article: Modules Java: Une application exemple – Conception
  • Modules Java: Une application exemple – Intérêts
  • 1. Fonctionnement de l’application

    Cette application exemple doit demander une entrée au format texte, la traiter et restituer un résultat. Elle est capable d’effectuer les traitements:
    1. Addition de deux entiers.
    2. Multiplication de deux entiers.
    3. Conjuguaison d’un verbe du 1er groupe au présent de l’indicatif.

    2. Conception de l’application

    En vertu du principe de la séparation des traitements et de la présentation il y aura une partie présentation et une autre de traitement. La partie présentation est simple dans notre cas, elle ne sera constituée que par un seul module. Toutefois, la partie traitement comporte deux types de traitements:
    1. Le calcul, qui sera constitué par un module.
    2. La conjugaison, qui sera constitué par un autre module.
    Cela fait trois modules différents. D’un autre côté, l’entrée étant en format texte brut, il nous faudra un quatrème module qui s’occupera de décortiquer cette entrée et faire la répartition des tâches en conséquence. En fin de compte, on doit avoir les modules suivants:
    1. Module principal qui prend en charge l’interface utilisateur: net.mtuto.superprocess
    2. Module de répartition: net.mtuto.repartition
    3. Module de calcul: net.mtuto.clacul
    4. Module de conjugaison: net.mtuto.conjugaison
    [caption id="attachment_1329" align="aligncenter" width="408"] Arbre de dépendance de l’application: en couleur verte les dépendances internes, en couleur rouge les dépendances au JRE.[/caption] Le module principal superprocess n’aura aucun lien avec les modules de traitement et aucune connaissance de leur existence. Ce module ne dépend que du module repartition. Le module repartition, de son côté, dépend des 2 modules de traitement. Enfin, les deux modules de traitement sont totalement indépendants l’un de l’autre.

    3. Le module conjugaison

    Ce module sera composé de deux packages:
    • net.mtuto.conjugaison.facade: joue le rôle d’interface et contient une classe unique Conjuguer
    • net.mtuto.conjugaison.traitement: contient les détails des traitements, effectués par la classe Groupe1
    Pour créer ce module, exécuter:
    mkdir -p application/spmodules/conjugaison/src/net/mtuto/conjugaison/facade
    mkdir -p application/spmodules/conjugaison/src/net/mtuto/conjugaison/traitement
    cd application/spmodules/conjugaison
    vi src/net/mtuto/conjugaison/facade/Conjuguer.java
    
    Saisir dans l’éditeur de texte le contenu de ce fichier de classe:
    package net.mtuto.conjugaison.facade;
    import java.util.*;
    import net.mtuto.conjugaison.traitement.Groupe1;
    public class Conjuguer {
      public List<String> groupe1pind(String verbe) {
        Groupe1 g1 = new Groupe1();
        return g1.presentind(verbe);
      }
    }
    
    Suite à cela, enregistrer et quitter, ensuite créer de la même manière la classe de traitement:
    vi src/net/mtuto/conjugaison/traitement/Groupe1.java
    
    Saisir dans l’éditeur de texte le contenu de ce fichier de classe:
    package net.mtuto.conjugaison.traitement;
    import java.util.*;
    public class Groupe1 {
      public List<String> presentind(String verbe) {
        String racine = verbe.substring(0, verbe.length() - 2);
        List<String> r = new ArrayList<String>();
        r.add("Je " + racine + "e");
        r.add("Tu " + racine + "es");
        r.add("Il " + racine + "e");
        r.add("Nous " + racine + "ons");
        r.add("Vous " + racine + "ez");
        r.add("Ils " + racine + "ent");
        return r;
      }
    }
    
    Après l’enregistrement de ce fichier de classe, on crée le descripteur du module:
    vi src/module-info.java
    
    Le contenu de ce fichier doit être:
    module net.mtuto.conjugaison {
      exports net.mtuto.conjugaison.facade;
    }
    
    Après avoir fini avec la création de ces trois fichiers sources, on peut construire le module dans application/spmodules/conjugaison.jar:
    mkdir classes
    javac -d classes src/module-info.java src/net/mtuto/conjugaison/facade/Conjuguer.java src/net/mtuto/conjugaison/traitement/Groupe1.java
    jar --create -M --file ../conjugaison.jar -C classes/ .
    

    4. Le module calcul

    Ce module sera composé, en plus du descripteur, par 3 classes:
    1. net.mtuto.calcul.facade.Calculer
    2. net.mtuto.calcul.traitement.Addition
    3. net.mtuto.calcul.traitement.Multiplication
    Pour créer ce module, il convient de procéder comme pour le module conjugaison: créer les dossiers puis les fichiers de chacun de ces éléments. Les contenus doivent être: Calculer.java:
    package net.mtuto.calcul.facade;
    import net.mtuto.calcul.traitement.Addition;
    import net.mtuto.calcul.traitement.Multiplication;
    public class Calculer {
      public String addition(String opAddition) {
        Addition addition = new Addition();
        String[] parties = opAddition.split(" ");
        int a = Integer.parseInt(parties[0]);
        int b = Integer.parseInt(parties[2]);
        return addition.additionner(a,b).toString();
      }
      public String multiplication(String opMultiplication) {
        Multiplication multiplication = new Multiplication();
        String[] parties = opMultiplication.split(" ");
        int a = Integer.parseInt(parties[0]);
        int b = Integer.parseInt(parties[2]);
        return multiplication.multiplier(a,b).toString();
      }
    }
    
    Addition.java:
    package net.mtuto.calcul.traitement;
    public class Addition {
      public Integer additionner(int a, int b) {
        return a + b;
      }
    }
    
    Multiplication.java:
    package net.mtuto.calcul.traitement;
    public class Multiplication {
      public Integer multiplier(int a, int b) {
        return a * b;
      }
    }
    
    module-info.java:
    module net.mtuto.calcul {
     exports net.mtuto.calcul.facade;
    }
    
    Une fois ces quatre fichiers sources crées, on peut construire le module dans application/spmodules/calcul.jar:
    mkdir classes
    javac -d classes src/*.java src/net/mtuto/calcul/facade/*.java src/net/mtuto/calcul/traitement/*.java
    jar --create -M --file ../calcul.jar -C classes/ .
    

    5. Le module repartition

    Ce module sera composé par le descripteur et une seule classe: net.mtuto.repartition.facade.Repartiteur Pour le créer, il convient de procéder comme pour le module conjugaison: créer les dossiers puis les fichiers de chacun de ces éléments. Les contenus doivent être: Repartiteur.java:
    package net.mtuto.repartition.facade;
    import java.util.*;
    import net.mtuto.conjugaison.facade.Conjuguer;
    import net.mtuto.calcul.facade.Calculer;
    public class Repartiteur {
      public List traiter(String txt) {
        List r = new ArrayList();
        String[] parties = txt.trim().split(" ");
        if (parties.length == 1) {
          if (parties[0].length()>2 && parties[0].toLowerCase().substring(parties[0].length()-2, parties[0].length()).equals("er")) {
            Conjuguer c = new Conjuguer();
            r = c.groupe1pind(txt);
          } else r.add("Impossible de traiter les données fournies");
        } else if (parties.length == 3) {
          int a = -1;
          int b = -1;
          try {
            a = Integer.parseInt(parties[0]);
            b = Integer.parseInt(parties[2]);
          } catch (NumberFormatException nfe) {
            a = -1;
            b = -1;
            r.add("Impossible de traiter les données fournies: l'un des arguments numériques est faux ou négatif");
          }
          if (a>=0 && b>=0 && (parties[1].equals("+") || parties[1].equals("x"))) {
            Calculer c = new Calculer();
            if (parties[1].equals("x")) r.add(c.multiplication(txt));
            else r.add(c.addition(txt));
          } else r.add("Impossible de traiter les données fournies");
        } else r.add("Impossible de traiter les données fournies");
        return r;
      }
    }
    
    module-info.java:
    module net.mtuto.repartition {
      exports net.mtuto.repartition.facade;
      requires net.mtuto.calcul;
      requires net.mtuto.conjugaison;
    }
    
    Après la création de ces deux fichiers sources, on procède à la construction du module dans application/spmodules/repartition.jar:
    mkdir classes/
    javac --add-modules net.mtuto.conjugaison,net.mtuto.calcul -p ../ -d classes src/module-info.java src/net/mtuto/repartition/facade/Repartiteur.java
    jar --create -M --file ../repartition.jar -C classes/ .
    
    ]]>

    Modules Java: JRE modulaire

    construire un JRE personnalisé. Ce tutoriel fait partie d’une série de trois articles qui permettront de cerner ce nouveau concept afin de l’utiliser pour plus de robustesse et maintenabilité des applications développées en Java:

    1. Modules Java: Présentation et concepts
    2. Cet article: Modules Java: JRE modulaire
    3. Modules Java: Une application exemple – Conception
    4. Modules Java: Une application exemple – Intérêts

    1. Une structure plus simple et plus efficace du système.

    La meilleur façon d’appréhender cette évolution de la structure est de faire une comparaison de l’existant antérieurement (version 1.8-) avec la nouvelle structure. Pour effectuer cette comparaison, on doit disposer de deux versions de Java sur le système. Une 1.8- et une 9+. Exécuter les deux commandes, observer les résultats et comparer (adapter, si besoin, les numéros de version dans les chemins cibles aux versions installées):
    tree -d -I legal\|man\|docs /usr/lib/jvm/java-1.8.0-openjdk-amd64/
    tree -d -I legal\|man\|docs /usr/lib/jvm/java-9-openjdk-amd64/
    
    La commande tree n’est pas installée systématiquement sur le système utilisé. Si c’est le cas, installez la avec la commande: sudo apt install tree. Dans ces deux commandes on n’affiche que les dossiers et on exclu les dossiers: legal/, man/ et docs/ qui ne contiennent que de la documentation et sont hors d’intérêt pour cet exposé. Modules Java A gauche l’ancienne arborescence Java 1.8-, à droite la nouvelle de Java 9+. On peut constater dès le départ une nette simplification et une meilleur organisation de la structure des dossiers. Dans le détail:
    • L’apparition de deux nouveaus dossiers: conf/ destiné à contenir les fichiers de configuration et jmods/ destiné à contenir les fichiers des modules java du système, JRE et JDK.
    • Le déplacement de la partie configuration du JRE dans le dossier conf/
    • La disparition du dossier de la JRE.
    • Un seul dossier bin/ et un seul dossier lib/ destinés à acueillir respectivement les fichiers exécutables et les librairies essentiellement natives du JRE et du JDK.

    2. Une utilisation plus simple de Java 9+

    Si on considère que Java est installé dans le dossier JAVA_INSTALLATION/ (donc /usr/lib/jvm/java-9-openjdk-amd64/ pour un système Ubuntu/Debian):
    • Toutes les commandes exécutables du JRE et du JDK sont dans : JAVA_INSTALLATION/bin. Il suffit donc d’ajouter ce chemin à la configuration du système (chemin PATH).
    • Toutes les librairies natives du système sont dans JAVA_INSTALLATION/lib. Si besoin, il suffit d’indiquer ce chemin au système pour ce type de librairie.
    • Tous les modules (librairies Java du système) sont dans JAVA_INSTALLATION/jmods, il suffit donc d’utiliser ce chemin pour le paramètre –module-path de Java 9+.
    Maintenant, vérifions les contenus de ces trois dossiers importants. Adaptez, dans cette commande,  le numéro de version de Java dans le chemin cible. Cette version pourrait être  10 ou 11 (Novembre 2018):
    ls -l /usr/lib/jvm/java-9-openjdk-amd64/bin
    
    Le résultat partiel de cette commande est:
    -rwxr-xr-x 1 root root  10424 oct.   7 17:06 jaotc
    -rwxr-xr-x 1 root root  10368 oct.   7 17:06 jar
    -rwxr-xr-x 1 root root  10376 oct.   7 17:06 jarsigner
    -rwxr-xr-x 1 root root  10344 oct.   7 17:06 java
    -rwxr-xr-x 1 root root  10400 oct.   7 17:06 javac
    -rwxr-xr-x 1 root root  10408 oct.   7 17:06 javadoc
    -rwxr-xr-x 1 root root  10400 oct.   7 17:06 jlink
    -rwxr-xr-x 1 root root  10368 oct.   7 17:06 jmod
    
    On remarque la présence des exécutables java (JRE), javac (JDK), jlink (JDK)..
    ls -l /usr/lib/jvm/java-9-openjdk-amd64/lib
    
    Le résultat partiel de cette commande est:
    -rw-r--r-- 1 root root     22976 oct.   7 17:06 libjimage.so
    -rw-r--r-- 1 root root     96960 oct.   7 17:06 libnet.so
    -rw-r--r-- 1 root root     72672 oct.   7 17:06 libnio.so
    -rw-r--r-- 1 root root     10288 oct.   7 17:06 libprefs.so
    -rw-r--r-- 1 root root      6128 oct.   7 17:06 librmi.so
    
    On remarque la présence des librairies dynamiques partagées natives libnet.so, libnio.so, librmi.so ..
    ls -l /usr/lib/jvm/java-9-openjdk-amd64/jmods
    
    Le résultat partiel de cette commande est:
    -rw-r--r-- 1 root root 19040174 oct.   7 17:06 java.base.jmod
    -rw-r--r-- 1 root root   128362 oct.   7 17:06 java.logging.jmod
    -rw-r--r-- 1 root root   381849 oct.   7 17:06 java.rmi.jmod
    -rw-r--r-- 1 root root  6503544 oct.   7 17:06 jdk.compiler.jmod
    -rw-r--r-- 1 root root   245838 oct.   7 17:06 jdk.jartool.jmod
    -rw-r--r-- 1 root root   630021 oct.   7 17:06 jdk.jshell.jmod
    
    On remarque la présence des modules java.base (JRE), java.logging (JRE), jdk.jartool (JDK)..

    3. Le nouveau format de paquet JMOD

    A l’exploration du contenu du dossier jmods/, on remarque que les fichiers des modules JRE et JDK ont l’extension .jmod. Il s’agit d’un nouveau format introduit par la version 9 et qui comporte des capacités plus évoluées que celles du format JAR. Le format JMOD permet d’embarquer dans un même fichier les bytecode java compilés (.class) avec des fichiers statiques, qui peuvent servir à la configuration par exemple, ainsi que du code natif. Toutefois, les fichiers ainsi créés sont destinés seulement pour des environnements de développement. L’utilitaire jmod, fichier exécutable dans le dossier bin/, permet de gérer ce type de fichier. Notament, le paramètre create permet la création de ces paquets. Pour plus de détails à propos de son fonctionnement exécuter:
    jmod --help
    
    Enfin, la connaissance de la liste des modules disponibles pour le système installé ne passe pas forcément par l’exploration du dossier jmods/. On peut afficher cette liste avec la commande:
    java --list-modules
    

    4. Création d’une image exécutable personnalisée

    Le principe de fonctionnement de Java repose sur le code intermédiaire (bytecode) et l’environnement d’exécution (JRE). Ce principe permet d’exécuter le même programme, écrit une seule fois, sur des plateformes différentes. Le mécanisme prévoit que chaque plateforme, qui supporte Java, dispose de son JRE spécifique. En même temps tous les JRE doivent pouvoir exécuter le même bytecode qui résulte de la compilation des sources Java. Au fil du temps, des difficultés sont apparus. D’abord le JRE et son élément central, le fichier rt.jar, sont devenus imposants. Dans sa dernière version 1.8, rt.jar pèse 63Mo. En même temps les terminaux légers avec de faibles ressources (tablettes, smartphones, smartwatch..), commencent à prendre une place de plus en plus importante chez les utilisateurs finaux. La modularisation du JRE dans Java 9 permet, désormais, de créer une image compacte et optimisée capable d’exécuter un programme écrit en Java. Ceci consiste à créer un sous-ensemble du JRE qui ne comprend que le strict nécessaire au cas spécifique de l’application cible. La suite de cet exposé utilise l’application d’exemple créée dans le premier article qui traite des modules Java. L’application modulaire, exécutable étant créée, on procède à la génération de limage auto exécutable (JRE compris). Il faut commencer par se positionner dans le dossier où a été créée cette application. Ensuite, céez un sous dossier modules/:
    mkdir modules
    
    On crée, ensuite la version modulaire prête à l’emploi de notre application:
    jmod create --class-path lib/premiermodule.jar modules/net.meddeb.premiermodule.jmod
    
    Maintenant, on peut créer l’image auto-exécutable de l’application à l’aide de l’utilitaire jlink:
    jlink --module-path /usr/lib/jvm/java-9-openjdk-amd64/jmods/:modules/ --add-modules net.meddeb.premiermodule --launcher executeHello=net.meddeb.premiermodule/net.meddeb.hellomodule.Hello --output distribution
    
    Le paramètre –output de cette commande permet de définir le dossier où sera généré le résultat.Dans notre cas on a choisi un dossier qui s’appelle distribution/. Pour explorer le résultat de cette commande et voir ce qui a été généré:
    tree -L 2 distribution/
    
    Dans le résultat, remarquez le fichier exécutable executeHello qui est le nom donnée dans la commande de jlink : modules java L’exécution se fait par l’intermédiaire de la commande qui suit, comme s’il s’agissait d’un exécutable natif:
    distribution/bin/executeHello
    
    ]]>

    Installer Java 9 sur Debian Stretch

    Java 9 constitue une évolution majeure de ce language de programmation / environnement d’exécution. L’évolution la plus importante apportée par cette version est sans doute l’introduction de la notion de module.  Cette introduction a nécessité un changement radical dans la structure de son environnement d’exécution (JRE) mais aussi de son kit de développement (JDK). Ce changement constitue une véritable rupture dans la façon de concevoir une application ou une librairie Java. Malgrès les précautions prises et l’effort manifeste qui a été déployé pour assurer une migration en douceur de l’énorme quantité de code déjà en utilisation de ce système, cette migration reste compliquée et sera certainement assez étalée dans le temps (Java 9 est sorti en Septembre 2017). A l’heure actuelle, du fait de la rupture entre Java 1.8- et Java 9+, migrer les systèmes serveurs à Java 9+ est un exercice risqué. En même temps il est nécessaire d’effectuer des tests en profondeur avec cette version sur les applications et les librairies existantes pour pouvoir les faire évoluer. Le système GNU/Linux Debian, destiné principalement à des machines serveurs et fidèle à son attachement à la stabilité, n’est toujours pas passé à Java 9 et ne le sera, probablement, pas de sitôt. Toutefois il y a toujours la possibilité d’installer cette version de Java pour pouvoir effectuer des tests comme indiqué. Ce tutoriel montre la manière offerte par ce système pour installer la version 9 de Java.

    1. Le dépôt backports de Debian

    Java 9 a été retiré du dépôt backports de Debian, certainement à cause d’incohérence constatée avec le reste du système, notament avec des interfaces graphiques. Cette méthode n’est donc  plus applicable pour Debian Stretch. Toutefois la démarche pour l’installation de paquets expérimentaux reste valable. Pour effectuer des expérimentations  Java 9+ sur Debian il convient d’utiliser plutôt la version Buster (10) de cet OS, version encore en développement à ne pas utiliser en production.
    A l’installation d’un système Debian le fichier /etc/apt/sources.list référence les dépôts des paquets d’installation et de mise à jour du système. Dans ce fichier et vers la fin, on trouve:
    # stretch backports
    #deb http://http.debian.net/debian stretch-backports main
    
    La première ligne est le commentaire qui donne le nom de ce dépôt. C’est la deuxième ligne qui le référence. Si elle n’a pas été modifiée, cette ligne est commentée et ce dépôt est donc désactivé. La raison pour laquelle ce dépôt est désactivé par défaut est qu’il contient des fonctionnalités en version jugée insiffisament stable ou pouvant introduire des instabilités ou encore au stade expérimental. Ce dépôt ne doit pas être utilisé pour un système en production. A l’heure actuelle (Octobre 2018) Java 9 est dans cette situation du point de vue de Debian. Cette version se trouve donc dans ce dépôt. Vérifions le, cette ligne étant commentée et Java 9 n’ayant jamais été installé sur le système utilisé, exécuter les commandes:
    sudo apt update
    apt-cache search openjdk | grep -E ^openjdk-[0-9]*-jdk\|jre
    
    La réponse à la deuxième commande sera:
    default-jre - Standard Java or Java compatible Runtime
    default-jre-headless - Standard Java or Java compatible Runtime (headless)
    openjdk-8-jdk - OpenJDK Development Kit (JDK)
    openjdk-8-jdk-headless - OpenJDK Development Kit (JDK) (headless)
    openjdk-8-jre - OpenJDK Java runtime, using Hotspot JIT
    openjdk-8-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless)
    openjdk-8-jre-zero - Alternative JVM for OpenJDK, using Zero/Shark
    openjdk-8-jre-dcevm - Alternative VM for OpenJDK 8 with enhanced class redefinition
    
    Seule la version 1.8 est disponible.

    2. Installation de la version 9

    Pour pouvoir installer la version 9 de Java, il convient d’activer le dépôt backports. Cela se fait en décommentant la ligne qui le référence dans le fichier de configuration des dépôts des paquets système. On peut le faire avec un éditeur de texte ou avec la commande:
    sudo sed -i '/^#.*backports /s/^#//' /etc/apt/sources.list
    
    Après cela on refait une recherche des versions disponibles:
    sudo apt update
    apt-cache search openjdk | grep -E ^openjdk-[0-9]*-jdk\|jre
    
    Le résultat sera alors:
    default-jre - Standard Java or Java compatible Runtime
    default-jre-headless - Standard Java or Java compatible Runtime (headless)
    openjdk-8-jdk - OpenJDK Development Kit (JDK)
    openjdk-8-jdk-headless - OpenJDK Development Kit (JDK) (headless)
    openjdk-8-jre - OpenJDK Java runtime, using Hotspot JIT
    openjdk-8-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless)
    openjdk-8-jre-zero - Alternative JVM for OpenJDK, using Zero/Shark
    openjdk-8-jre-dcevm - Alternative VM for OpenJDK 8 with enhanced class redefinition
    openjdk-9-jdk - OpenJDK Development Kit (JDK)
    openjdk-9-jdk-headless - OpenJDK Development Kit (JDK) (headless)
    openjdk-9-jre - OpenJDK Java runtime, using Hotspot JIT
    openjdk-9-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless)
    
    La version 9 est maintenant bien disponible à l’installation. Pour l’installer:
    sudo apt install openjdk-9-jdk-headless
    
    L’installation du JDK 9 positionne également cette version comme étant la version par défaut. Pour s’en rendre compte, pour l’environnement d’exécution (JRE) et le compilateur Java:
    java -version # JRE
    javac -version # compilateur
    
    Les deux sont bien à la version 9 fraichement installée.

    3. Basculer entre les versions 9 et 1.8

    S’agissant d’une installation d’expérimentation, on peut bien avoir besoin de basculer entre les deux versions installées. Pour cela on utilise l’utilitaire update-java-alternatives. Exécuter:
    sudo update-java-alternatives --list
    
    La réponse sera alors:
    java-1.8.0-openjdk-amd64       1081       /usr/lib/jvm/java-1.8.0-openjdk-amd64
    java-1.9.0-openjdk-amd64       1091       /usr/lib/jvm/java-1.9.0-openjdk-amd64
    
    Le numéro correct de la version est 9 et non 1.9 car Java a changé la politique du numéro de version à partir de celle là. Debian, pour le moment, ne semble pas en tenir compte mais cela sera peut être rectifié à l’avenir. En tout cas cela n’empêche rien de fonctionner. L’élément important dans ce résultat est la première colonne. le nom affiché dans cette colonne sert à définir la version par défaut. Pour basculer vers la version 1.8:
    sudo update-java-alternatives --set java-1.8.0-openjdk-amd64
    
    La valeur utilisée pour le paramètre –set est le nom affiché dans la première colonne de la liste.]]>

    Eclipse et les modules Java

    Photon – 4.8 (Juin 2018), inclu des fonctionnalités de configuration des projets Java pour prendre en charge la notion de module introduite en Java 9.

    1. Un projet de démonstration

    Dans eclipse créer un nouveau projet Java: Sélectionner le menu : File / New / Project.Sélectionner Java Project. Ensuite, l’utilisation du bouton Next permet d’accéder à la fenêtre de configuration du projet. eclipse modules java Saisir le nom du projet et sélectionner la version 9 de Java au minimum comme cible d’exécution. Utiliser le bouton Finish pour lancer la création du projet. A ce stade, une fenêtre s’affiche pour suggérer la création du fichier descripteur de module module-info.java du projet: eclipse modules java Si on aurait choisit Java 1.8 ou inférieur comme version cible d’exécution, cette fenêtre ne s’afficherait pas. Pour le besoin de démonstration, on va décliner cette offre et continuer avec un projet compatible Java 1.8 et inférieur. On clique donc sur Don’t Create et Eclipse génère le projet comme demandé. Sélectionner le nouveau projet dans l’explorateur de package (volet de gauche) et faire un click droit de la souris puis accéder au menu de création d’une nouvelle classe: New / Class. eclipse modules java Ce menu mène à la fenêtre de cration d’une nouvelle classe: eclipse modules java Eclipse ajoute le fichier de la nouvelle classe qu’il convient de compléter comme suit (ligne à rajouter): eclipse modules javaA ce statde la création du projet de démonstration est terminée. On peut l’exécuter pour afficher le message de test. On se propose dans la suite de transformer ce projet en module Java.

    2. Le fichier descripteur de module

    Si le projet Eclipse est un module Java, il faudra qu’il inclut le fichier descripteur de module module-info.java dans la racine du projet (voir l’article à ce sujet). Une fonctionnalité Eclipse permet de créer ce fichier. eclipse modules java Click droit sur le projet, ensuite Configure / Create module-info.java. L’utilisation de ce menu permet d’accéder à la même fenêtre de création de module rencontrée à la première génération du projet. Dans cette fenêtre saisir net.meddeb.eclipsemoduletuto comme nom de module et valider avec le bouton Create. eclipse modules java Eclipse génère, alors, le fichier descripteur de module avec le nom saisi. Ce fichier généré exporte l’unique package de notre projet. On constate que cette transformation en module ne change rien à cette application et elle continue à s’exécutable de la même manière. Pour le besoin de démonstration, on va faire évoluer cette application de manière qu’elle affiche un autre message en passant par l’API de logging de Java. Modifier le fichier de la classe Mainclass pour que son contenu devienne (copier / coller):
    package net.meddeb.eclipsemoduletuto;
    import java.util.logging.Logger;
    public class Mainclass {
      public static void main(String[] args) {
        System.out.println("Hello eclipse Java !");
        Mainclass m = new Mainclass();
        m.afficherMessage();
      }
      public void afficherMessage() {
        final Logger logger=Logger.getLogger(this.getClass().getPackage().getName());
        logger.info("Hello eclipse Java module !");
      }
    }
    
    Après cette modification la compilation de l’application échoue. La raison est que le package java.util.logging utilisé par cette modification n’est pas accessible du fait de la modularisation du code. La solution est de déclarer implicitement le besoin de notre application/module à ce package. Pour le faire, modifier le fichier descripteur de module de manière que son contenu devienne:
    module net.meddeb.eclipsemoduletuto {
      exports net.meddeb.eclipsemoduletuto;
      requires java.logging;
    }
    
    L’explication est que le code supplémentaire utilisé se trouve dans le package  java.util.logging, exporté par le module du JRE java.logging. Pour pouvoir l’utiliser il faudra inclure ce module dans une clause requires du fichier descripteur. Une question légitime se pose dans ces conditions: pourquoi l’application se compilait et s’exécutait sans problème avant cette modification, alors qu’aucune clause requires n’a été utilisée ? La réponse à cette question est que le code de définition de la classe et le code de System.out utilisé pour l’affichage du message se trouvent dans le module de base du JRE java.base. C’est l’unique module qui est référencé implicitement et n’a pas besoin de figurer explicitement dans une clause requires du module qui l’utilise. D’un autre côté, pour que la compilation et l’exécution donctionnent, l’emplacement des fichiers des modules utilisés doit figuer dans le chemin de recherche des modules: le Modulepath.

    3. Modulepath et Classpath

    Faire un click droit sur le projet dans le volet de gauche, puis accéder au menu Build Path / Configure Build Path.. eclipse modules java La fenêtre de configuration des chemins de construction du projet apparaît et on peut remarquer l’existence de deux types de chemins. Le chemin des modules utilisés Modulepath et le chemin de compatibilité le Classpath.eclipse modules java Eclipse a référencé pour nous la totalité du JRE cible de l’exécution dans le Modulepath du projet. Ouvrir le contenu de ce JRE, les deux modules utilisés par le projet apparaîssent bien dans cet endroit: ]]>

    Modules Java: Présentation et concepts

    les modules. Cet article a pour objectif d’expliquer ce nouveau concept, ses raisons d’être et surtout son utilité. Ce tutoriel fait partie d’une série de trois articles qui permettront de cerner ce nouveau concept afin de l’utiliser pour plus de robustesse et maintenabilité des applications développées en Java:

    1. Cet article: Modules Java: Présentation et concepts
    2. Modules Java: JRE modulaire
    3. Modules Java: Une application exemple – Conception
    4. Modules Java: Une application exemple – Intérêts

    1. La programmation orientée objet et l’encapsulation

    Java s’inscrit entièrement dans la programmation orientée objet (POO). Mis à part les 8 types de données primitives et qui ont tous un correspondant objet, dans Java, tout est objet. La POO est basée sur un certain nombre de concepts parmis lesquels le concept d’encapsulation vise à favoriser la robustesse et la modularité du code. Ce concept consiste, principalement, à cacher les détails des données (champs) et des traitements (méthodes) à l’intérieur de l’objet lui même. Concrètement, cela consiste à déclarer ces détails avec les modificateurs de visibilité private ou protected. Cela consiste, par conséquent, à ne laisser accessible de l’extérieur de l’objet que le strict nécessaire à son utilisation. Cela veut dire, ne déclarer avec le modificateur public que le strict nécessaire à l’utilisation externe. Un objet d’une classe conçue en conformité avec ce principe de la POO, peut être modifié sans aucune conséquence pour les reste du code, tant qu’on ne modifie pas le format de sa partie publique. Cela dote l’application d’une grande robustesse et évolutivité au cours de son cycle de vie, car le code d’une application a toujours besoin d’être modifié pour corriger des anomalies ou faire évoluer son fonctionnement.

    2. Extension de la notion d’encapsulation

    Dans la pratique, il s’est avéré que la classe est d’une granularité trop faible au sein d’un logiciel pour pouvoir assurer une réutilisabilité et une robustesse suffisante. Ceci est la conséquence naturelle de la compléxité et la taille grandissantes des logiciels à construire. De nos jours, une application moyenne peut rapidement atteindre plusieurs centaines de classes. Pour cela le niveau d’abstraction de l’encapsulation gagnerait en efficacité s’il est étendu à un niveau plus élevé: Le package, qui regroupe plusieurs classes dans un seul espace de noms. Dans un package, une classe peut être déclarée:
    • Avec le modificateur public: ses méthodes et ses champs publiques sont accessibles à partir des autres packages.
    • Sans modificateur: N’est pas accessible à partir des autres packages.
    Ceci fait du package une unité de modularisation possible d’un code logiciel. On peut considérer une bibliothèque/composant logiciel composée d’un package unique où une ou plusieurs classes publiques constituent l’interface du composant et d’autres classes non publiques chargées des détails internes à cette bibliothèque/composant. Malgrés une nette amélioration apportée par ce nouveau niveau d’abstraction, cette extension reste insuffisante, notamment pour les raisons suivantes:
    • La nouvelle unité modulaire est forcément mono-package.
    • Absence de règles explicites de dépendance entre packages.
    • Absence de toute vérification préalable de cohérence entre unités modulaires.
    Notamment, la dernière raison évoquée engendre la possibilité de conflits détéctables seulement pendant l’exécution, engendrant ainsi des erreurs d’exécution. Ce problème est connu sous le nom de JAR Hell ou l’enfer des JARs.

    3. Les modules en Java 9

    Les modules introduits en Java 9 portent la possibilité d’encapsulation à un niveau d’abstraction encore plus élevé. C’est au niveau physique du fichier JAR que l’encapsulation peut s’effectuer. Mais au delà de l’encapsulation, une modularisation avancée du code applicatif devient possible grâce aux caractéristiques des modules Java:
    • Groupement de code applicatif sous forme d’une collection de packages de tailles quelconques.
    • Le module peut contenir aussi du code natif, des fichiers de configuration et des ressources statiques.
    • Visibilité externe déclarative de type exports/requires qui permet la construction facile d’un arbre de dépendance.
    • Vérification, préalable à l’exécution, de la cohérence des inter-dépendances entre modules.
    Concrètement, un fichier JAR/module ne diffère d’un fichier JAR classique que par l’existence à sa racine d’un fichier qui s’appelle impérativement module-info.java. Ce fichier contient une définition déclarative du module.

    4. Création d’un module Java

    Avec un éditeur de texte, créer le fichier Hello.java dont le contenu est:
    package net.meddeb.hellomodule;
    class Hello {
      public static void main(String[] args) {
        System.out.println("Hello module in world !");
      }
    }
    
    Et le fichier module-info.java dont le contenu est:
    module net.meddeb.premiermodule {
      exports net.meddeb.hellomodule;
    }
    
    Ensuite, compiler le code et créer le fichier JAR du module en exécutant cette série de 3  commandes dans l’odre indiqué:
    javac -d classes Hello.java module-info.java
    mkdir lib
    jar -c -M -f lib/notrepremiermodule.jar -C classes .
    
    Voilà! On vient de créer notre premier module Java 9 dans le dossier lib/
    • Ce module s’appelle net.meddeb.premiermodule, mais le nom peut être quelconque. Dans la pratique il vaut mieux le préfixer par un nom de domaine pour l’unicité universelle.
    • Le fichier JAR qui contient le module s’appelle: notrepremiermodule.jar
    • Il contient un seul package net.meddeb.hellomodule, qui contient lui-même une seule classe Hello.
    • Il exporte son unique package: déclaration exports dans le fichier module-info.java
    Vérifier le contenu du fichier JAR de ce module. Exécuter:
    jar tvf lib/notrepremiermodule.jar
    
    Alors, la réponse est:
       233 Wed Sep 26 21:26:36 CEST 2018 module-info.class
         0 Wed Sep 26 21:26:22 CEST 2018 net/
         0 Wed Sep 26 21:26:22 CEST 2018 net/meddeb/
         0 Wed Sep 26 21:26:22 CEST 2018 net/meddeb/hellomodule/
       450 Wed Sep 26 21:26:22 CEST 2018 net/meddeb/hellomodule/Hello.class
    
    Maintenant, on peut exécuter la classe Hello de deux manières différentes. En tant que classe exécutable (contenant la méthode main()) dans un fichier JAR quelconque, compatibilité ascendante oblige:
    java --class-path lib/notrepremiermodule.jar net.meddeb.hellomodule.Hello
    
    On peut l’exécuter, également, en tant que classe exécutable contenue dans un module:
    java --module-path lib/ -m net.meddeb.premiermodule/net.meddeb.hellomodule.Hello
    
    Cette deuxième commande ne fonctionne qu’en JAVA 9+. Le succès de son exécution, montre que le JRE reconnait bien notre module en tant que tel. Elle ne fonctionnera pas si on enlève du  JAR le fichier module-info.class. Vous pouvez le tester, il suffi de repackager le JAR sans ce fichier.]]>

    Tutoriel JShell: Personnalisation et import de librairies externes

    JShell aborde des aspects avancés de l’utilisations de cet outil. On y traitera les sujets de l’import et le référencement de bibliothèques (et de modules) externes à la JRE ainsi que celui de la personnalisation de l’outil. Ce tutoriel fait partie d’une serie de quatre articles qui permettront de se familiariser, suffisament, avec JShell afin de pouvoir l’utiliser efficacement et augmenter la productivité avec le langage de programmation Java:

    1. Tutoriel JShell: Installation et concepts
    2. Tutoriel JShell: Utilisation
    3. Tutoriel JShell: Scripts et scripts de démarrage
    4. Cet article: Tutoriel JShell: Personnalisation et Import de librairies externes

    1. Import de packages supplémentaires

    Lancer JShell sans aucun script de démarrage, sans même DEFAULT. Ceci est possible grâce à la commande:
    jshell --no-startup
    
    Ensuite exécuter les deux instructions Java suivantes, l’une après l’autre:
    String nomfichier = "fichiertest.txt"
    Files.write(Paths.get(nomfichier), "Le contenu du fichier de test\n".getBytes());
    
    La première instruction s’exécute sans erreur. La raison est que la classe String est dans le package java.lang qui n’a pas besoin d’être importé explicitement. En revanche, la deuxième instruction échoue car les classes Files et Paths sont dans le package java.nio.file. Comme dans les programmes Java, pour utiliser ce package dans JShell, on doit d’abord l’importer. Exécuter maintenant:
    import java.nio.file.*
    Files.write(Paths.get(nomfichier), "Le contenu du fichier de test\n".getBytes());
    
    Astuce: La deuxième commande peut être rappelée avec la touche flèche haut du clavier comme dans le shell système. Maintenant, cette commande s’exécute et crée le fichier fichiertest.txt avec le contenu demandé. Pour vérifier, quitter JShell et retrouvez ce fichier dans le dossier courant.

    2. Import de packages et bibliothèques externes

    De la même manière que pour les packages Java, on peut référencer les packages d’une bibliothèque externe. Pour pousser cette logique à son extrême, on va créer une « bibliothèque » Java personnelle. Pour simplifier, cette« bibliothèque » sera composée d’une seule classe mais packagée dans un fichier JAR comme il se doit. A l’aide d’un éditeur de texte, créer le fichier Multiplication.java dont le contenu est:
    package net.meddeb.mult;
    public class Multiplication {
      public void table(int i) {
        for (int j=1; j<=9; j++) {
          System.out.printf("%d x %d = %d\n", i, j, i*j);
        }
      }
    }
    
    C’est l’unique classe qui compose la « bibliothèque » de notre exemple. Elle ne contient qu’une methode unique qui affiche la table de multiplication de l’entier passé en paramètre. On va compiler et packager dans le fichier multip.jar cette classe. Ensuite on va l’installer dans un dossier lib/, sous dossier de notre dossier de travail. Pour faire tout cela, exécuter dans l’ordre:
    javac Multiplication.java
    mkdir -p net/meddeb/mult/
    mv Multiplication.class net/meddeb/mult/
    jar -c -f multip.jar -M net/
    mkdir lib/
    mv multip.jar lib/
    
    Pour pouvoir utiliser cette « bibliothèque » personnelle avec JShell, on doit le lancer avec la commande:
    jshell --class-path lib/multip.jar
    
    Pour tester l’utilisation de la classe Multiplication, exécuter dans l’ordre:
    import net.meddeb.mult.*
    Multiplication m = new Multiplication()
    m.table(8)
    
    Le paramètre de lancement –class-path permet d’indiquer un ou plusieurs packages externes (à la JRE). Ce paramètre peut également être positionné dans l’environnement JShell grâce à la commande /env. Ceci est particulièrement intéréssant pour inclure ce paramètre dans un script de démarrage par exemple ou même dans un script tout court. Un exemple: créer le fichier de script mult5.jsh dont le contenu est le suivant:
    /env -class-path lib/multip.jar
    import net.meddeb.mult.*;
    Multiplication m = new Multiplication();
    m.table(5);
    /exit
    
    Ensuite exécutez le avec la commande simple:
    jshell mult5.jsh
    
    De la même manière on peut importer des modules, au sens du nouveau concept introduit par Java 9 du terme module. Ceci peut être fait grâce aux paramètres –module-path et –add-modules.

    3. Personnalisation de l’environnement JShell

    Cette personnalisation consiste en la modification à souhait du mode de retour d’information ou feedback. Il existe trois paramètres à modifier pour le retour d’information: le prompt ou le message d’attente, la troncature et le format. Un mode natif de JShell ne peut pas être supprimé mais on peut le copier, modifier cette copie et l’activer à la place du mode natif. Ensuite le mode natif peut être réactivé à tout moment.

    3.1. Personnalisation du message d’attente (prompt)

    Dans l’environnement JShell exécuter:
    /set prompt
    
    La réponse à cette commande est:
    |  /set prompt normal "\njshell> " "   ...> "
    |  /set prompt silent "-> " ">> "
    |  /set prompt concise "jshell> " "   ...> "
    |  /set prompt verbose "\njshell> " "   ...> "
    Il s’agit de la liste des formats du message d’attente pour les quatres modes de retour d’information prédéfinis: normal, silent, concise et verbose.  Pour commencer la personnalisation, on va copier le mode normal pour créeer le mode personnalisé modetuto. Pour cela exécuter:
    /set mode modetuto normal -command
    
    Maintenant, on peut modifier le message de prompt de ce mode pour le personnaliser. Deux paramètres sont à modifier: le message d’attente normal et celui à utiliser dans le cas de saisie de plusieurs lignes de suite, comme dans le cas de céation de méthode. Exécuter:
    /set prompt modetuto "\n[Tutoriel JShell:] "  "[...............:] "
    
    Pour tester le nouveau prompt, il faut l’activer:
    /set feedback modetuto
    
    JShell passe immédiatement au nouveau mode qui est identique au mode normal sauf en ce qui concerne le message de prompt car on l’a modifié.

    3.2. Personnalisation de la troncature

    Le paramètre de troncature permet de personnaliser l’affichage des valeurs trop larges (généralement des chaines de caractères) en tronquant cet affichage. Exécuter:
    /set truncation modetuto
    
    Le retour de cette commande indique les valeurs actuelles de ce paramètre qui sont hérités du mode normal. Ces valeurs veulent dire:
    1. Toute valeur à afficher dont la taille est supérieure ou égale à 80 caractères sera tronquée.
    2. Une exception est faites pour l’affichage des expressions qui ne sera tronquée qu’à partir de 1000 caractères.
    Pour tester ce comportement, exécuter:
    String b = String.join("", Collections.nCopies(7, "abcdefghij"));
    String b = String.join("", Collections.nCopies(8, "abcdefghij"));
    System.out.println(String.join("", Collections.nCopies(8, "abcdefghij")));
    
    abcdefghij, sont les 10 premiers caractères de l’alphabet. La première instruction permet de dupliquer cet ensemble de caractères 7 fois, ce qui donne 70 caractères. Le résultat de cette instruction n’est pas tronqué. En revanche celui de la deuxième instruction qui donne 80 caractères est tronqué. La troisième instruction affiche le résultat d’une expression, la limite est donc repoussée à 1000, c’est pour cela que le résultat de cet affichage n’est pas tronqué. Modifions maintenant ce paramètre:
    /set truncation modetuto 30
    String b = String.join("", Collections.nCopies(3, "abcdefghij"));
    
    La deuxième instruction permet de vérifier que la troncature est bien effectuée à partir de 30 caractères.

    3.3. Personnalisation du format

    Exécuter dans l’environnement JShell:
    /set format modetuto
    
    La réponse montre la richesse et la compléxité de ce paramètre. Ceci provient du fait qu’il concerne l’affichage des messages de retour des commandes dans différentes circonstances. Faute de pouvoir aborder les détails de ce paramétrage, on va se contenter d’un un exemple. Pour le détail il faudra consulter l’aide de ce paramètre qui peut être affiché avec:
    /help format
    
    Pour l’exemple, exécuter:
    import java.net.*
    
    Cette commande ne retourne aucun message d’information, même pas pour dire que tout s’est bien passé. On peut remedier à cela en personnalisant le paramètre format de notre modetuto:
    /set format modetuto display "{pre}added import {name}{post}" import-added
    /set format modetuto display "{pre}re-added import {name}{post}" import-modified,replaced
    
    Après cela, exécuter une deuxième fois la commande d’import. Grâce à cette personnalisation, on a désormais un message de retour claire.

    4. Persistence de la personnalisation de l’environnement JShell

    Si on quitte JShell après avoir personnalisé l’environnement tel que cela a été vu dans le paragraphe précédent, la personnalisation sera perdue. Pour faire perdurer ce paramétrage il faudra exécuter:
    /set mode modetuto -retain
    
    Avec cette commande, même si on quitte JShell, le paramétrage modetuto n’est pas perdu mais ne sera pas activé par défaut au démarrage. Pour le rendre actif de manière permanente, il faudra exécuter:
    /set feedback modetuto -retain
    
    ]]>

    Tutoriel JShell: Script et scripts de démarrage

    JShell aborde des aspects avancés de l’utilisations de cet outil. On y traitera les sujets de la création de scripts Java avec JShell et celui des scripts de démarrage. Ce tutoriel fait partie d’une serie de quatre articles qui permettront de se familiariser, suffisament, avec JShell afin de pouvoir l’utiliser efficacement et augmenter la productivité avec le langage de programmation Java:

    1. Tutoriel JShell: Installation et concepts
    2. Tutoriel JShell: Utilisation
    3. Cet article: Tutoriel JShell: Scripts et scripts de démarrage
    4. Tutoriel JShell: Personnalisation et Import de librairies externes

    1. Scripts JShell

    Lancer JShell et exécuter les trois instructions Java suivantes, l’une après l’autre dans l’ordre indiqué:
    char c = 'D'
    int rang = (int)c - 64
    System.out.printf("D est la lettre de rang %d dans l'alphabet\n", rang)
    
    Comme attendu, à chaque fois JShell répond immédiatement pour donner le résultat de l’instruction. Maintenant on peut visualiser l’ensemble de ces instructions par:
    /list
    
    On obtient une liste numérotée de l’historique des instructions exécutées depuis le début de la session (remarquer l’ajout automatique des points-virgules à la fin des lignes):
       1 : char c = 'D';
       2 : int rang = (int)c - 64;
       3 : System.out.printf("D est la lettre de rang %d dans l'alphabet\n", rang);
    
    Il est possible de sauvegarder cette suite d’instructions Java dans un fichier pour constituer un script JShell:
    /save premierscript.jsh
    
    Pour vérifier le succés de l’opération, quitter Jshell (commande /exit) et visualiser le contenu du fichier de script fraîchement créé:
    cat premierscript.jsh
    
    Bien entendu on aurait pu, également, créer ce script directement avec un éditeur de texte. Maintenant qu’on a ce fichier de script on peut l’exécuter à partir du Shell système par:
    jshell premierscript.jsh
    
    Cette commande donne le résultat:
    D est la lettre de rang 4 dans l'alphabet
    |  Welcome to JShell -- Version 10.0.2
    |  For an introduction type: /help intro
    jshell>_
    La première ligne de la réponse montre bien que le script a été exécuté. Le problème est qu’on se trouve à la fin dans l’environnement de l’outil. Ce comportement n’est souvent pas souhaitable. Pour remedier à cet inconvénient, on doit modifier le contenu du script. Quitter JShell, éditer le fichier premierscript.jsh pour y ajouter à la fin la commande JShell: /exit. Avec cette nouvelle version du script, on retournera au Shell système à la fin de l’exécution. Deux enseignements sont à retenir de cette manipulation:
    1. On doit ajouter la commande /exit à la fin d’un script si on souhaite se retrouver dans le Shell système à la fin de son exécutionn, ce qui est souvent le cas.
    2. On peut combiner des instructions Java et des commandes JShell dans un script JShell.

    2. Scripts de démarrage

    Un script de démarrage JShell est un script tel qu’on vient de le voir dans le paragraphe précédent qui est exécuté automatiquement par l’environnement de l’outil dans les deux cas suivant:
    1. Au démarrage de l’environnement en utilisant le paramètre –startup <NomDuScript>
    2. A l’exécution de l’une des commandes: /env, /reset et /reload
    Ce comportement est utile pour paramétrer à souhait JShell. Il existe trois scripts de démarrage prédéfinis qui peuvent assurer un paramétrage minimal:
    1. DEFAULT : exécuté par défaut, même si on ne l’invoque pas. C’est à dire même si on n’utilise pas le paramètre –startup pour lancer JShell.
    2. PRINTING : Assure un paramétrage qui facilite l’affichage des données
    3. JAVASE : Permet d’utiliser toutes les librairies de Java SE
    Pour s’en rendre compte, exécuter:
    jshell --startup PRINTING
    
    Cette commande, lance JShell avec le script de démarrage prédéfini PRINTING (au lieu de DEFAULT donc). Exécuter ensuite:
    printf("Le nombre %d est le double du chiffre %d", 10, 5)
    
    Cette instruction n’est pas conforme à la syntaxe Java, pourtant elle s’exécute sans erreur. Pour illucider ce mystère, exécuter dans l’ordre:
    /reset
    /open PRINTING
    /list
    
    La première comande permet de remettre l’environnement à zéro. La seconde permet de charger le script PRINTING. La troisième liste le contenu de ce script. On se rend compte, grâce à ces commandes, que le script PRINTING définit une série de méthodes qui servent toutes à l’affichage de données dont la méthode utilisé printf(…). Ceci explique l’exécution sans erreur de cette instruction. Quitter JShell et relancer le sans aucun paramètre:
    jshell
    |  Welcome to JShell -- Version 10.0.2
    |  For an introduction type: /help intro
    
    jshell>_
    
    Exécuter maintenant:
    printf("Le nombre %d est le double du chiffre %d", 10, 5)
    
    On obtient le message d’erreur:
    |  Error:
    |  cannot find symbol
    |    symbol:   method printf(java.lang.String,int,int)
    |  printf("Le nombre %d est le double du chiffre %d", 10, 5)
    |  ^----^
    
    Ce qui est tout à fait normal puisque cette fois c’est DEFAULT qui s’est exécuté au démarrage et il ne contient pas la définition de cette méthode. Vérifions:
    /reset
    /open DEFAULT
    /list
    
    Le script DEFAULT se contente d’importer un nombre limité de packages du JRE. On peut conclure que par défaut on ne peut utiliser que ces packages (en plus de java.lang.* qui est utilisable sans besoin de l’importer) dans l’environnement JShell.

    3. Scripts de démarrage personnalisés

    De même qu’avec les scripts prédéfinis, on peut utiliser un script personnalisé pour le démarrage de JShell. Généralement, le script de démarrage sert à configurer l’environnement pour mieux l’utiliser: import de packages, référencement de modules, définitions de méthodes.. Lancer JShell sans aucun paramètre. Dans ces conditions, c’est le script de démarrage par défaut qui sera utilisé. Ensuite, exécuter l’instruction Java suivante, qui permet de déterminer si un site web répond correctement en renvoyant le statut HTTP d’une requête GET:
    HttpClient.newHttpClient().send(HttpRequest.newBuilder().uri(new URI("http://www.meddeb.net")).build(), HttpResponse.BodyHandler.asString()).statusCode()
    
    L’exécution de l’instruction échoue. La raison de cet echec est l’utilisation de classes Java qui font partie d’un module et de packages non visibles dans l’environnement JShell. De plus, aucun des 3 scripts prédéfinis ne permet d’importer les pré-requis nécessaires à cette exécution. A l’aide d’un éditeur de texte crééer un ficher de script personnalisé http.jsh, dont le contenu est:
    /env --add-modules jdk.incubator.httpclient
    import jdk.incubator.http.*
    import java.net.*
    
    Lancer JShell avec ce script de démarrage personnalisé:
    jshell --startup http.jsc
    
    Maintenant JShell est configuré convenablement pour exécuter la commande précédente qui a échoué. Relancer cette commande. Si le site fonctionne normalement au moment de l’exécution, la réponse sera égale à 200, le code qui correspond à un statut « Tout va bien ».]]>

    Tutoriel JShell: Utilisation

    JShell est une démonstration par la pratique de l’utilisation (et l’utilité) de cet outil. Comme c’est souvent le cas dans ce site personnel, c’est une succession d’exercices pratiques à effectuer et se rendre compte du résultat par soit même. Il s’agit ici de l’utilisation courante de l’outil dans ses deux volets: les commandes JShell et l’exéction d’instructions Java. Cet tutoriel fait partie d’une serie de quatres articles qui permettront de se familiariser, suffisament, avec JShell afin de pouvoir l’utiliser efficacement et augmenter la productivité avec Java:

    1. Tutoriel JShell: Installation et concepts
    2. Cet article: Tutoriel JShell: Utilisation
    3. Tutoriel JShell: Scripts et scripts de démarrage
    4. Tutoriel JShell: Personnalisation et Import de librairies externes

    1. Les variables dans JShell

    Quand on exécute une instruction Java qui retourne une valeur, JShell crée automatiquement une variable pour y stocker cette valeur. Exécuter dans l’environnement JShell:
    10 + 15
    
    L’affichage de retour indique qu’une variable qui s’appelle $1 contient le résultat de cette opération simple:
    $1 ==> 25
    
    On peut obtenir plus d’information à ce sujet grâce à la commande JShell:
    /vars
    
    Cette commande affiche la liste des variables en indiquant leurs types. On peut également améliorer le retour d’information immédiat. Exécuter:
    /set feedback verbose
    
    Si on exécute, après cela, l’opération d’addition modifiée:
    10.2 + 15.3
    
    Maintenant le retour immédiat est plus explicite. JShell nous informe tout de suite qu’il a créé une variable $2 de type double et affiche son contenu. Remarquer que les variables créées implicitement portent un nom qui commence par le caractère $ suivi par un indice qui s’incrémente à chaque nouvelle création. On remarque également que le type de la variable créée s’adapte au type du résultat de l’instruction exécutée. Les variables peuvent être également indiquées explicitement avec un nom plus significatif. Dans ce cas le type doit être également indiqué:
    double rayon = $2
    
    Vérifier avec la commande /vars que le contenu de la variable rayon est bien égale à celui de la variable $2. Le résultat d’une instruction peut également être affecté à une variable explicite:
    double surface = Math.pow(rayon,2) * Math.PI
    
    Ce qui calcule la surface d’un cercle en élevant son rayon au carré et en multipliant le résultat par π .

    2. Vérifier le comportement de Java avec JShell

    Une des applications importantes de JShell est de pouvoir vérifier rapidement les résultats retournés par des instructions Java. Exécuter les trois instructions suivantes l’une après l’autre:
    Math.round(23.4999999)
    Math.round(23.5000001)
    Math.round(23.5000000)
    
    Ce test montre que la methode Math.round(double) retourne bien la valeur arrondie à l’entier le plus proche du décimal passé en paramètre (les deux premières exécutions). Il montre surtout que si la valeur est à mi-chemin entre deux valeurs entières, c’est l’entier supérieur qui est retourné. Maintenant, on se propose de tester un comportement, souvent mal compris, des instances de classes. Mais d’abord partons d’un environnement propre. Exécuter:
    /vars
    /list
    
    La session JShell accumule, au fur et à mesure qu’on l’utilise, une liste de variables et d’instructions (snippets) qui ont été créées et exécutées. On peut remettre l’environnement à zéro, notamment pour commencer de nouveaux tests. Pour cela exécuter:
    /reset
    
    Après une éventuelle vérification, exécuter les instructions dans l’ordre:
    Date d1 = new Date()
    Date d2 = d1
    Date d3 = (Date)d1.clone()
    /vars
    
    La dernière commande permet d’afficher les 3 variables d1, d2 et d3 avec leur valeur qui est la même. Maintenant, modifions la valeur de d1 pour mettre l’heure à minuit:
    d1.setHours(0)
    /vars
    
    L’explication du résultat, qui est tout à fait normal, est que la variable qui contient une instance de classe n’est en réalité qu’une référence d’un emplacement mémoire qui contient, lui, les données de cette instance. Il faut comprendre le contenu de ces variables comme une adresse de mémoire et non l’instance elle-même. Ceci dit, les trois instructions font ce qui suit:
    1. Créé une instance de la classe Date et affecte la référence à cette instance (adresse mémoire de l’instance) à la variable d1.
    2. Affecte la valeur de d1 à d2. C’est la référence à l’instance qui est affectée. Donc d1 et d2 pointent sur la même instance.
    3. La méthode clone() créé une nouvelle instance dont les données sont identiques à l’instance clonée. d3 contient donc la référence d’une autre instance de la classe Date.

    3. Tester le code d’une méthode

    A titre d’exemple, on se propose de développer une méthode qui calcule la clé d’un numéro de sécurité sociale en France. Bien qu’assez triviale, cette méthode nécessite plusieurs instructions qui s’enchaînent. Pour saisir l’ensemble du code, suivre le contenu qui correspond à une ligne de code par l’appui sur la touche [ENTREE], comme dans tout IDE. Commençon par la première ligne:
    long cle(String numero) {
    
    Après la touche [ENTREE], JShell répond par:
    ...>
    
    Et attend la suite des lignes. Continuer la saisie, ligne par ligne:
    ...> String n = numero.replaceAll("2A", "18");
    ...> n = n.replaceAll("2B", "19");
    ...> long ln = Long.parseLong(n);
    ...> long resultat = 97 - (ln % 97);
    ...> return resultat;
    ...> }
    
    A la saisie de la dernière ligne qui constitue, syntaxiquement, la fin de la méthode, JShell termine la saisie et affiche un message qui nous informe (sous reserve qu’il n’y a pas d’erreur de saisie) que la méthode a été créée. On peut le vérifier par la commande:
    /methods
    
    On peut également l’exécuter:
    cle("2690299341732")
    
    Bien entendu vous pouvez utiliser votre propre numéro de sécurité sociale (les 13 chiffres) pour calculer et vérifier la clé. Ceux qui sont nées en Corse remarqueront qu’il y a un bug dans ce code qu’il faudra, donc, corriger. Pour le faire on doit éditer la méthode. C’est la commande edit qui permet cela. D’abord parametrons un éditeur de texte disponible sur le système utilisé. Pour ma part j’utilise vi:
    /set edit vi
    
    Puis
    /edit cle
    
    L’éditeur paramétré s’ouvre avec le texte du code de la méthode et on peut alors le modifier. Remplacer le nombre 18 dans la 2eme ligne par 19 et le nombre 19 dans la 3eme ligne par 18. Ensuite enregistrer la modification et quitter.]]>

    Tutoriel JShell: Installation et concepts

    JShell est l’utilitaire Shell (ou REPL, pour Read Eval Print Loop) du langage de programmation Java. Un tel outil manquait cruellement à Java jusqu’à sa récente version 9 où il a été introduit. JShell permet d’augmenter considérablement la productivité du développement avec ce langage. Il permet, notament, d’ffectuer de manière très simple, rapide et intéractive des tests d’exécution d’instructions Java (snippets). Cet article a pour objectif de présenter la manière d’installer ainsi que les concepts d’utilisation de cet outil. Il fait partie d’une serie de quatre articles qui permettront de se familiariser, suffisament, avec JShell afin de pouvoir l’utiliser efficacement et augmenter la productivité avec Java:

    1. Cet article: Tutoriel JShell: Installation et concepts
    2. Tutoriel JShell: Utilisation
    3. Tutoriel JShell: Scripts et scripts de démarrage
    4. Tutoriel JShell: Personnalisation et Import de librairies externes

    1. Installer JShell

    JShell fait partie du JDK. Il a été introduit à la version 9 de Java et de ce fait il faudra installer au minimum la version 9 du JDK pour pouvoir y avoir accéder. Sur Ubuntu installer la variante headless d’OpnenJDK 9 ou plus.
    sudo apt install openjdk-11-jdk-headless
    
    Après cela JShell sera disponible. La vérification peut se faire par la commande:
    jshell -version
    
    Cette commande répond par l’affichage de la version de JShell. La version majeure retournée (premier nombre) doit être la même que celle du JDK installé.

    2. Les bases de fonctionnement de JShell

    JShell est utilisé, principalement, de manière interactive. Son interface est une console interactive en ligne de commande. Pour y accéder utiliser la commande:
    jshell
    
    Suite à cela on obtient le prompt suivant qui attend la saisie de commandes à la syntaxe JShell ou Java.
    |  Welcome to JShell -- Version 10.0.1
    |  For an introduction type: /help intro
    
    jshell> _
    
    A chaque commande introduite (saisie de la commande + [Entrée] du clavier), JShell répond immédiatement par l’affichage du résultat de la commande ou par un message d’erreur si la syntaxe est fausse ou qu’une erreur d’exécution s’est produite.
    • Les commandes propres à JShell commencent toujours par le caractère slash: /
    • Toute commande introduite qui ne commence pas par ce caractère doit être conforme à la syntaxe Java.

    3. Les commandes JShell

    Ces commandes qui commencent obligatoirement par le caractère slash servent à contrôler le fonctionnement de l’outil. Une commande particulière permet d’avoir une aide à propos de ces commandes:
    /help
    
    Affiche une liste exhaustive des commandes disponibles (relative à la version utilisée, bien entendu). On peut également afficher l’aide d’une commande en particulier quand on connait sa syntaxe:
    /help exit
    
    Affiche une aide sur la commande JShell exit. Grâce à cette commande, on sait maintenant qu’il faudra utiliser la commande /exit pour quitter la console JShell. De la même manière, déterminer la fonction de la commande history. Ensuite utiliser cette commande pour voir le résultat:
    /history
    
    Cette commande permet d’afficher l’hitorique de toutes les commandes exécutées depuis le début de la session JShell courante. Si tout va bien le résultat devrait être donc:
    /help
    /help exit
    /history
    

    4. Exécuter une instruction Java

    Dans la console JShell exécuter la commande:
    exit
    
    La réponse est un message d’erreur. Pourtant, on l’a vu, exit est bel et bien une commande correcte de JShell. L’explication est dans l’absence du caractère slash au début. Cette absense fait en sorte que JShell considère qu’il s’agit d’une instruction Java. exit n’est pas une instruction Java valide, d’où l’erreur. Exécuter maintenant l’instruction (faire attention à la syntaxe, le mieux est de faire un copier/coller):
    "Java JShell".substring(5,11)
    
    La réponse doit être:
    $1 ==> "JShell"
    
    Cette réponse signifie que maintenant une variable appelée $1 contient la valeur chaine de caractère ‘JShell‘. Explication: A l’exécution de l’instruction Java qui retourne un résultat, JShell crée une variable qui s’appelle $x, x étant un nombre entier, et lui affecte le résultat de l’instruction. Ensuite il affiche le contenu de cette variable. Pour s’en rendre compte, exécuter maintenant:
    System.out.println($1)
    
    La méthode substring(iDebut, iFin) de la classe String, retourne une sous chaine de caractère qui commence à l’indice de cracatère iDebut et fini à l’indice iFin. D’où le résultat obtenu. Exécuter maintenant l’instruction modifiée:
    "Java JShell".substring(5,12)
    
    L’indice de caractère (de fin) maximum dans cette chaine étant 11 (car elle contient 11 caractères), cette instruction lève l’exception java.lang.StringIndexOutOfBoundsException exactement comme à son exécution dans un programme Java.]]>