Accueil Nos publications Blog Java 9 : À la découverte de JShell 1/3

Java 9 : À la découverte de JShell 1/3

Introduction

Java 9 est sorti le 21 septembre 2017 avec son lot de nouveautés.
Parmi ces nouveautés figure la création d’un REPL (Read Evaluate Print Loop) appelé JShell. Il s’agit d’un outil de commande en ligne à partir duquel il est possible d’évaluer du code Java sans avoir besoin d’écrire un programme complet (créer une classe avec des méthodes, le compiler, etc.).

Cet article est le premier d’une série de trois sur JShell. Dans cette première partie nous allons apprendre à utiliser JShell pour évaluer du code Java. La seconde partie sera consacrée à la gestion du code par JShell (sauvegarde des commandes entrées dans un fichier, import de code depuis l’extérieur…) et enfin en dernière partie nous verrons comment personnaliser la configuration de JShell.

Présentation de JShell

Pourquoi avoir introduit un REPL en Java ?
De nombreux langages de programmation intègrent un interpréteur de code en ligne. C’est le cas de Lisp, Python ou encore Ruby. Cet outil facilite grandement l’apprentissage d’un langage car il permet d’entrer une expression et d’afficher le résultat à la volée.
Prenons un exemple en Python. Pour tester l’affichage d’un Hello World il suffit d’ouvrir l’interpréteur Python et de taper la commande suivante print :

Avant Java 9, tester l’affichage d’un bout de code affichant Hello World était plus compliqué car cela nécessitait de créer une classe qu’il fallait ensuite compiler puis exécuter :

Le fait de devoir rédiger tout un programme pour tester un simple bout de code (sans compter si l’on veut y faire des modifications) rend l’apprentissage des bases plus compliqué pour des étudiants voulant s’initier aux langages de programmation. La plupart des universités/écoles en ont bien eu conscience et c’est l’une des raisons pour lesquelles elles préfèrent ne pas enseigner le langage Java au cours des premières années d’études.

Cette contrainte a été remontée à la communauté Java, ce qui a conduit à la création de JShell, afin de simplifier l’apprentissage du langage.

Architecture de Jshell

Avant d’entrer dans les détails de l’architecture de JShell, commençons par un schéma représentatif. Cela permettra d’avoir une vue d’ensemble :

Lorsque du code est entré dans la console de JShell, celui-ci est traité par JLine. Il s’agit d’une librairie Java permettant de gérer les saisies sur console (c’est la version 2 de cette librairie qui est d’ailleurs utilisée par JShell).
Une fois le code saisi, celui-ci est parsé par le parseur de JShell afin d’en déterminer le type (méthode, variable…) puis est enveloppé dans une classe en respectant les règles suivantes :
* Tous les imports sont placés en haut de cette classe.
* Les variables, méthodes et déclarations de classes deviennent des membres statiques de cette classe.
* Les expressions et déclarations sont enveloppées dans une méthode à l’intérieur de cette classe.
Après cette étape, le code source généré est analysé et compilé en Bytecode par le compilateur Java, qui est ensuite envoyé à un processus exécutant la JVM afin de charger et exécuter le code.

Lancement de JShell

Maintenant que nous avons vu l’architecture de JShell, il est temps pour nous de le lancer.
Pour ce faire, rendez-vous sur le site de Java afin de télécharger et installer la JDK 9, puis configurez votre variable d’environnement JAVA_HOME afin qu’elle pointe vers le répertoire de votre JDK. Après cela, il vous faudra également créer une variable d’environnement pointant vers l’exécutable de votre JShell. Vous le trouverez dans le répertoire jdk-9.[Version_installé]\bin .

Une fois l’installation terminée, ouvrez une invite de commande et tapez la commande jshell.

Vous devriez obtenir le résultat suivant :

Nous y voilà ! Après avoir entré la commande, JShell démarre avec un message de bienvenue.
Si vous veniez à rencontrer un message d’erreur de type “*‘jshell’ is not recognized as an internal or external command, operable program or batch file.” c’est que le chemin vers l’exécutable de JShell n’est pas correctement configuré dans votre variable d’environnement PATH.

Nous y sommes, maintenant que JShell est lancé nous allons pourvoir apprendre à l’utiliser.

Entrons maintenant dans la première partie de cet article, comment déclarer du code dans JShell.

Prêts ? Allons-y !

Déclarer du code dans JShell

Au démarrage de JShell, vous avez une invite de commande en attente de saisie.
Il existe deux types de commandes que l’on peut saisir :
* Les commandes internes à JShell, celles-ci commencent par un “/”
* Du code Java

Pour notre premier test, nous allons commencer par le traditionnel Hello World.
Pour ce faire, tapez la commande suivante :
System.out.println(“Hello World!”);

Vous devriez obtenir le résultat suivant :

…c’est tout ! Pas besoin d’importer de bibliothèques ou de définir une classe avec une méthode main() ou autre. La commande à elle seule suffit à afficher notre message et c’est bien là que se situe l’intérêt de JShell.
Alors comment cela fonctionne ?
Ce qu’il faut savoir, c’est qu’au démarrage de JShell, certaines librairies sont pré-chargées afin de faciliter l’exécution des commandes. Pour connaître ces librairies, il suffit de taper la commande /list -start (nous reviendrons plus tard sur la commande /list mais comme vous pouvez le constater elle commence par un slash, il s’agit donc d’une commande interne à JShell). Vous devriez obtenir la sortie suivante :

Cette commande liste toutes les librairies chargées au démarrage de JShell. Parmi ces librairies figure java.io.* ce qui explique pourquoi la commande println est reconnue et exécutée sur l’invite de commande.

Quels codes peut-on saisir dans l’invite de commande ?
Il s’agit du code suivant :

  • Importationde librairies
  • Déclaration de Classes
  • Déclaration d’Interfaces
  • Déclaration de méthodes
  • Déclaration de variables
  • Déclaration de constantes
  • Déclaration d’expressions

1.1 Évaluation d’expressions

Afin d’évaluer une expression dans JShell, il suffit de l’entrer directement dans l’invite de commande. Prenons un exemple simple en tapant “1 + 1”. Vous devriez obtenir l’affichage suivant :

En entrant cette commande, JShell a évalué l’expression saisie puis affiché le résultat correspondant en sortie, tout en assignant le résultat à une variable nommée “$1”, et comme vous pouvez le constater, pas besoin de finir la commande avec un ” ;”, JShell l’ajoute pour vous.
Lorsque vous entrez une expression, JShell vérifie également qu’elle est complète. Si ce n’est pas le cas vous obtiendrez à la ligne suivante la sortie “..>” signifiant que votre expression n’est pas finie et le JShell se met en attente de complément.
Voici un exemple en entrant seulement “1 +” en entrée :

En fin de commande, Jshell affiche le résultat en le stockant dans une nouvelle variable nommée $2.

Vous pouvez entrer n’importe quelle expression Java dans l’invite de commande.
La capture suivante vous montre quelques exemples :

Il est tout à fait possible de se servir des variables créées par JShell afin d’effectuer des opérations dessus. Voici des exemples d’utilisation :

Lors de la saisie d’une expression arithmétique, JShell affiche un résultat bref : “Variable ==> résultat“, sans détail sur le type de la variable créée par exemple.
Et bien il est tout à fait possible de demander à JShell d’afficher davantage d’informations sur l’exécution des commandes saisies, et comme cela nous sera utile pour la suite du tutoriel, voyons cela maintenant.
Pour obtenir plus d’informations sur les commandes exécutées, il suffit d’entrer la commande /set feedback verbose (la commande set est précédé d’un “/”, il s’agit donc d’une commande interne à JShell).
Maintenant si l’on saisit une commande, nous obtenons une sortie différente :

JShell nous indique avoir créé une variable $12 de type double.
C’est plus explicite ainsi !

À présent, voyons comment créer nos propres variables.

1.2 Déclaration de variables

La déclaration de variables s’effectue de la même manière que dans un programme Java.
À noter toutefois que dans JShell vous n’êtes pas autorisé à créer des variables static ou final pour la simple et bonne raison que le but de JShell et de vous permettre de créer des variables indépendantes dont vous pouvez tester et modifier le résultat à n’importe quel moment. Si vous tentez toutefois de créer des variables ainsi, les mots-clés “static” et “final” seront ignorés et vous obtiendrez en sortie un message d’avertissement.
La capture ci-dessous présente différents types de créations de variables :

Avec le mode “verbose” activé, JShell vous affiche explicitement l’ancien type et l’ancienne valeur de votre variable, en vous indiquant qu’elle a été remplacée par une nouvelle valeur du type correspondant.
Si vous souhaitez maintenant supprimer la variable créée, vous pouvez le faire à l’aide de la commande /drop [nom_variable] :

Sur des programmes courts, il est simple de se souvenir des variables créées. Mais qu’en est-il si vous effectuez des tests avec de nombreuses variables ? Il serait compliqué de les rechercher dans l’historique des commandes tapées, n’est-ce pas ?
Et bien JShell a pensé à tout ! Grâce à une commande interne, /vars, il est possible de lister l’ensemble des variables créées dans JShell.

Cette commande s’utilise ainsi :

  • /vars
  • /vars [ID]
  • /vars [Nom_Variable]
  • /vars -start
  • /vars -all

/vars : permet d’afficher la liste de toutes les variables actives de la session en cours.

/vars [ID] : permet d’afficher la variable et sa valeur, correspondant à l’ID renseigné. Cet ID correspond au nom de la variable qu’a affecté JShell à votre expression ($1, $2…).

/vars [Nom_variable] : permet d’afficher la variable [Nom_variable] et sa valeur.

/vars -start : permet d’afficher toutes les variables que vous avez ajouté au script de démarrage de JShell (ce point sera expliqué dans la troisième partie, dans le chapitre Personnaliser le démarrage du JShell mais pour faire simple, sachez qu’il est possible de créer des variables dans un fichier puis de le charger au démarrage du JShell).

/vars – all : permet d’afficher la liste de toutes les variables actives, inactives et chargées au démarrage.

La figure ci-dessous vous montre des exemples d’utilisation de cette commande :

Maintenant que nous savons créer des variables sur JShell, voyons à présent comment créer des méthodes.

1.3 Déclarer des méthodes

Dans JShell, il est possible de déclarer et d’appeler des méthodes sans avoir à les déclarer dans une classe. Pour cela il suffit de les entrer tout comme nous le ferions dans une classe Java.
Voici un exemple simple :

Lors de la création d’une méthode, vous avez la possibilité de faire appel à une variable pas encore déclarée. JShell créera la méthode en vous affichant un message d’avertissement indiquant qu’elle a bien été créée, mais vous ne pourrez pas l’utiliser tant que la variable appelée n’est pas déclarée :

Dans cet exemple, nous avons créé une méthode addition() qui prend en paramètre un entier x, puis l’additionne avec un entier y. La méthode est bien créée mais JShell vous avertit qu’elle ne pourra pas être utilisée tant que nous n’aurons pas défini la variable y.

SI l’on essaye tout de même d’utiliser la méthode, nous obtenons un message d’erreur indiquant qu’il nous faut d’abord déclarer y.
SI maintenant nous déclarons la variable y et que nous rappelons la méthode addition, JShell nous renvoie bien le résultat voulu.

De la même manière, il est possible d’appeler une méthode à l’intérieur d’une autre. La méthode principale sera créée mais ne pourra pas être utilisée tant que la sous-méthode appelée ne sera pas définie :

Dans cet exemple, nous déclarons une méthode operation() qui va dans un premier temps faire la somme de ses deux premier arguments, puis multiplier le résultat obtenu avec la variable z via la méthode multiply().
La méthode est bien créée, toutefois JShell nous envoie un message d’avertissement nous indiquant que nous ne pourrons pas nous en servir tant que nous n’aurons pas défini la méthode multiply().

SI nous essayons tout de même de nous servir de la méthode operation(), nous obtenons un message d’erreur nous indiquant que nous devons d’abord définir la méthode multiply().
Ce n’est qu’une fois celle-ci définie que nous pouvons à présent appeler la méthode operation() et obtenir le résultat voulu.

Nous avons vu comment évaluer des expressions, créer des variables ainsi que des méthodes. Maintenant voyons comment créer des classes.

1.4 Déclaration de Classes

Il est possible de créer des Classes, Interfaces ou encore des Enums à l’intérieur de JShell. Pour ce faire, il suffit simplement d’entrer le code source correspondant dans l’invite de commande, tout comme nous le ferions dans un fichier ou dans notre IDE.

Dans l’exemple qui suit, nous allons créer une enum Operation qui va énumérer les opérations d’addition et de multiplication. Nous allons ensuite créer une interface ICompute afin d’effectuer un calcul en fonction de l’opération désirée. Pour finir, nous allons créer une classe Compute qui va implémenter l’interface Icompute. Elle aura pour variable x et y, et effectuera un calcul en fonction de l’opération voulue.

Commençons par l’enum :

Passons maintenant à l’interface ICompute :

Pour finir, passons à la création de la Classe Compute, implémentant l’interface précédemment créée :

Ça y est ! Nous venons de créer notre Enum, notre Interface et notre Classe directement depuis JShell. Maintenant il ne nous reste plus qu’à tester l’ensemble. Pour ce faire, nous allons créer un objet Compute en initialisant ses variables x et y, et nous allons lui demander dans un premier temps d’additionner ses variables, puis de les multiplier :

Et voilà ! Nous avons rédigé notre code tout comme nous l’aurions fait depuis notre IDE et nous avons pu le tester directement sans avoir à le compiler, celui-ci ayant été directement exécuté à la volée.

Vous vous souvenez de la commande /vars expliquée plus haut ? Et bien il existe son équivalent pour les types créés dans JShell, elle se nomme /types et fonctionne de la même manière :

  • /types
  • /types [ID]
  • /types [Nom_Types]
  • /types -start
  • /types -all

/types : permet de lister l’ensemble des types (Class, Interface, Enum) actifs créés dans JShell.

/types [ID] : permet d’afficher le type correspondant à l’id [ID].

/types [Nom_Type] : permet d’afficher le type correspondant à [Nom_Type].

/types -start : permet de lister les types qui ont été ajoutés au script de démarrage de JShell (tout comme évoqué plus haut, l’ajout d’un script au démarrage du JShell sera expliqué dans la troisième partie, dans le chapitre Personnaliser le démarrage du JShell).

/types -all : permet de lister l’ensemble des types de la session en cours (actifs, inactifs et chargés au démarrage de JShell).

La capture suivante vous donne un exemple d’utilisation de ces commandes :

(Noter que la commande /types -start ne renvoie rien, ce qui est normal puisque nous n’avons chargé aucun type au démarrage de JShell. Comme évoqué plus haut nous verrons un peu plus loin dans ce tutoriel comment faire).

1.5 Importer des librairies

Au démarrage de JShell, plusieurs librairies sont importées par défaut.
Pour savoir lesquelles, il suffit d’entrer la commande /imports.
Voici ce que nous avons au démarrage d’une nouvelle session JShell :

Tous les types présents dans ces packages peuvent être utilisés lors de la session JShell en cours.
Mais alors, qu’en est-il des autres ?

Prenons un exemple simple, supposons que nous voulons créer un objet LocalDate (pour rappel : cet objet a été introduit en Java 8).
À la création, nous obtenons le message d’erreur suivant :

JShell vous informe qu’il ne peut exécuter votre commande car il ne reconnaît pas LocalDate.
Celui-ci se trouvant dans le package java.time, nous devons d’abord l’importer dans notre session en cours.
L’import s’effectue de la même manière que dans une classe Java :
import java.time.LocalDate

Une fois la commande saisie, si nous tentons à nouveau de créer notre variable localDate, JShell parvient bien à exécuter notre commande :

Cela fonctionne pour tous les types ne faisant pas partie des librairies importées par défaut.
Vous devrez vous-même importer les librairies correspondantes avant de pouvoir les utiliser.

Conclusion

Nous avons vu comment évaluer rapidement des expressions à la volée, comment créer des variables, méthodes et des types dans JShell, et enfin comment importer des librairies.

Lorsque nous travaillons avec peu de code tapé dans JShell, il est facile de retrouver les différentes commandes entrées. Mais lorsque l’on commence à tester des programmes un peu plus long, cela peut devenir plus fastidieux.
Pas de panique, JShell a pensé à tout. Au cours de la deuxième partie, nous allons étudier comment JShell gère le code source saisi dans l’invite de commande.
Nous verrons entre autres comment lister l’historique des commandes saisies, comment éditer ces commandes à partir d’un éditeur (oui oui c’est possible!), comment enregistrer dans un fichier l’ensemble des commandes saisies et surtout, comment les charger lors d’une nouvelle session JShell.

© SOAT
Toute reproduction interdite sans autorisation de l’auteur