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
- 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.