Java 9 – JShell

Java 9 propose un nouvel outil dans le JDK nommé JShell. Il est défini dans la JEP 222
JShell est un outil de type REPL :

  • Read
  • Eval
  • Print
  • Loop

C’est un outil qui boucle continuellement pour lire la saisie de l’utilisateur, l’évaluer et afficher le résultat sur la sortie. Plusieurs langages possèdent un outil de type REPL (Scala, Groovy, Python, Lisp, …). Les shells Unix sont aussi des REPL.
 

Présentation de JShell

JShell est un outil en ligne de commande fourni avec le JDK qui permet de tester du code Java de manière dynamique et simplifiée. Il est ainsi facile de tester une API ou de développer un petit prototype. C’est donc une console interactive qui accepte des portions de code Java et les évalue en temps réel pour indiquer le résultat.
JShell n’est pas un IDE mais un outil en ligne de commande. Il propose cependant des fonctionnalités pour faciliter la saisie du code : historique des saisies avec édition, complétion de code en utilisant la touche tabulation, ajout automatique des points virgules terminaux nécessaires, importations, …
Deux types d’éléments peuvent être saisis et interprétés par JShell :

  • des fragments (snippets) : ce sont des portions de code Java (expressions, instructions, définition de variables, de méthodes, de classes, d’imports)
  • des commandes : elles commencent toutes pas un slash

 
Pour lancer JShell, il suffit de lancer la commande jshell dans une console si le sous-répertoire bin d’un JDK 9 est inclus dans le PATH :

C:\java>java -version 
java version "9.0.1" 
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

C:\java>jshell 
|  Welcome to JShell -- Version 9.0.1 
|  For an introduction type: /help intro 

jshell> 

Il est possible de lancer JShell avec l’option -v pour demander le mode verbeux : dans ce mode, JShell affiche des informations complémentaires sur les actions qu’il réalise.

C:\java> jshell -v
|  Welcome to JShell -- Version 9.0.1
|  For an introduction type: /help intro

jshell> 

La commande /help permet d’obtenir de l’aide.
Pour quitter JShell, il suffit d’utiliser la commande /exit.

jshell> /exit
|  Goodbye

C:\java>

 

Les fragments

Un fragment correspond à un extrait de code Java qui peut être :

  • La déclaration d’un import
  • La déclaration d’une classe, d’une interface ou d’une énumération
  • La déclaration d’une méthode
  • La déclaration d’un champ
  • Une instruction
  • Une expression
  • Une valeur primitive

Il est possible d’évaluer des expressions. Le résultat est affecté à une variable temporaire unique commençant par $ suivi d’un numéro correspondant à l’identifiant du fragment.

jshell> 1+2
$1 ==> 3

jshell> "bonjour"
$2 ==> "bonjour"

Il est possible de définir explicitement le type et le nom de la variable.

jshell> int i = 5;
i ==> 5

Il est possible de saisir des instructions.

jshell> System.out.println($1 + " "+$2)
3 bonjour

Les instructions peuvent être saisies sur plusieurs lignes. Le prompt de JShell est différent pour saisir les autres lignes du fragment.

jshell> if (i  $2 = "petit";
   ...> } else {
   ...> $2 = "grand";
   ...> }

Il est naturellement possible d’invoquer les méthodes sur les objets référencés par les variables.

jshell> $2.toUpperCase()
$4 ==> "BONJOUR"

Le point-virgule de fin d’instruction est facultatif pour les expressions ou les instructions uniques sur une seule ligne en dehors d’un bloc de code.
Il est possible de définir des méthodes

jshell> long additionner(int a, int b) {
   ...> return (long) a + b;
   ...> }
|  created method additionner(int,int)

Une fois définie, il est possible d’invoquer la méthode.

jshell> additionner(1,2)
$6 ==> 3

Il est aussi possible de définir des types : classes, interfaces ou énumérations. Il n’est cependant pas possible de définir de packages.
 

Les commandes

Toutes les commandes commencent par un caractère slash. Elles permettent d’interagir avec JShell et son environnement.
La commande /vars affiche les variables définies dans la session.

jshell> /vars
|    int $1 = 3
|    String $2 = "petit"
|    int i = 5
|    long $6 = 3
|    String $7 = "BONJOUR"

La commandes /methods affiche les méthodes définies dans la session.

jshell> /methods
|    long additionner(int,int)

La commande /types affiche les classes, interfaces et énumérations définies dans la session.
La commande /list affiche les fragments saisis sans la session précédés de leur identifiant.

jshell> /list

   1 : 1+2
   2 : "bonjour"
   3 : System.out.println($1 + " "+$2)
   4 : int i = 5;
   5 : long additionner(int a, int b) {
       return (long) a + b;
       }
   6 : additionner(1,2)
   7 : $2.toUpperCase()
   8 : if (i <= 10) {
       $2 = "petit";
       } else {
       $2 = "grand";
       }

La commande /imports affiche les imports définis dans la session.

jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
|    import java.nio.file.*
|    import java.util.*
|    import java.util.concurrent.*
|    import java.util.function.*
|    import java.util.prefs.*
|    import java.util.regex.*
|    import java.util.stream.*

Les imports ci-dessus sont automatiquement définis au lancement de JShell. Pour ajouter d’autres imports, il suffit d’utiliser l’instruction import

jshell> import java.time.*

jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
|    import java.nio.file.*
|    import java.util.*
|    import java.util.concurrent.*
|    import java.util.function.*
|    import java.util.prefs.*
|    import java.util.regex.*
|    import java.util.stream.*
|    import java.time.*

La commande /edit suivi de l’identifiant d’un fragment ou du nom de l’élément ouvre une fenêtre pour modifier son code.
La commande /exit permet de quitter JShell.
La commande /drop suivi de l’identifiant d’un fragment permet de le supprimer de la session.
La commande /save enregistre les fragments de la session dans un fichier texte dont le nom est fourni après la commande.

jshell> /save code.jsh

La commande /open permet de lire et d’exécuter les fragments préalablement enregistrés dans le fichier dont le nom suit la commande.

jshell> /open code.jsh
3 petit

La commande /reset réinitialise la session.
La commande /history affiche l’historique des informations saisies dans JShell.
La commande /help affiche l’aide en ligne.
La commande /set permet de configurer certains éléments de JShell.
Par exemple, il est possible de préciser l’éditeur utilisé par la commande /edit avec /set editor

jshell> /set editor notepad
|  Editor set to: notepad

Il est aussi possible de définir le mode de verbosité des messages de JShell avec /set feedback en précisant le mode souhaité (concise, normal, silent, verbose).

jshell> /set feedback verbose
|  Feedback mode: verbose

jshell> 2 + 3
$10 ==> 5
|  created scratch variable $10 : int

 

Les fonctionnalités avancées de JShell

JSHell propose de nombreuses fonctionnalités pour saisir le code comme l’assistance à l’achèvement de code (completion code) ou de commandes grâce à la touche tabulation, des raccourcis clavier, des raccourcis pour les commandes, l’ajout des imports requis, la définition de variables, …
Il propose aussi des fonctionnalités avancées, notamment :

  • La redéfinition d’une variable, d’une méthode ou d’une classe. Cette redéfinition peut aller jusqu’à un changement du type d’une variable ou la signature d’une méthode. Attention, il est possible que la redéfinition de l’élément soit incompatible avec la définition existante et empêche donc son exécution correcte sans d’autres modifications.
  • L’utilisation des références à venir (forward references) : elles permettent dans le corps d’une méthode d’utiliser des classes, des méthodes ou des variables qui ne sont pas encore définies. Bien sûr, elles devront l’être avant de pouvoir exécuter le code.
  • Les exceptions de type checked levées par un fragment de code sont gérées par JShell
    jshell> Thread.sleep(5000);

    jshell>

    Dans un bloc de code, les exceptions de type checked doivent être gérées (capture ou propagation comme cela a toujours été le cas en Java).

JShell propose aussi une API qu’il est possible d’utiliser dans une application.

jshell> import jdk.jshell.*

jshell> JShell js = JShell.create()
js ==> jdk.jshell.JShell@c540f5a

jshell> List snippets = js.eval("1+2")
snippets ==> [SnippetEvent(snippet=Snippet:VariableKey($1)#1-1 ... ,causeSnippetnullvalue=3)]

jshell> snippets.stream().map(s -> s.value()).forEach(System.out::println)
3

 

Conclusion

JShell est un outil particulièrement utile dans plusieurs situations :

  • Apprendre Java
  • Tester une portion de code
  • Expérimenter une API

Comme il est fourni avec le JDK 9, il est intéressant de savoir le mettre en œuvre et de l’utiliser.