Sortir Java de sa coquille : découvrir jshell

Christophe Vaudry
norsys-octogone
Published in
13 min readFeb 16, 2022

Une REPL en Java ?

Quand on a besoin de tester ou d’expérimenter rapidement un bout de code il est intéressant d’avoir une REPL (Read-Eval-Print Loop) : un shell interactif dédié à un langage dans lequel on peut saisir et évaluer du code pour avoir un feedback immédiat. De nombreux langages et environnements de développement en proposent une comme par exemple Ruby, Python ou encore Groovy, Scala et Clojure pour des langages de l’écosystème Java.

D’ailleurs cela remonte au premier temps de l’informatique numérique (fin de la décennie 1950) avec LISP qui se distinguait de langages comme Fortran ou COBOL par cet aspect justement. Ainsi en Clojure — un dialecte moderne de LISP que l’on trouve sur la JVM — ces afficionados promeuvent le “REPL Driven Development”.

Il est des situations où l’on souhaite tester rapidement un petit bout de code sans pour autant initialiser un projet complet dans son IDE préféré. Une REPL peut alors être un outil pratique dans ces cas-là.

Meme de Morpheus dans le film Matrix : “ What if I told you Java has a REPL”

Le JDK propose une REPL depuis sa version 9 — prénommée jshell vous vous en doutiez — même si elle semble assez peu connue et utilisée (du moins parmi les développeurs Java que je côtoie).

L’objet de cet article est de présenter les principales fonctionnalités de cet outil afin de pouvoir l’utiliser rapidement en cas de besoin. L’idée est de fournir un petit aide-mémoire sur les options et les commandes de base.

TLDR : le strict minimum à savoir

L’outil jshell fait partie des binaires du JDK et peut être lancé depuis un shell ou une invite de commande juste en tapant jshell.

Pour l’aide, il faut lancer jshell avec les options--help, -h ou -?, par exemple jshell -?.

Une fois dans jshell, les commandes spécifiques à jshell commencent par un /. Les 2 commandes à connaître au minimum sont /help pour avoir des informations sur les commandes et /exit pour pouvoir sortir proprement d’une session.

Et comme c’est une REPL, vous pouvez en plus des commandes jshell, saisir du code Java qui sera interprété.

C’est vraiment le minimum à savoir pour pouvoir commencer à l’utiliser si vous êtes pressés. Dans la suite de l’article, on prend le temps de présenter jshell plus en détail.

Lancement

L’outil jshell fait partie des binaires distribués dans votre JDK et devrait être dans le PATH si java et javac le sont. Dans la copie d’écran ci-après, on peut voir que l’exécutable est dans le répertoire bin de votre distribution java (GraalVM ici) comme javac et java.

Répertoire bin d’une distribution Java, avec tous les binaires des outils dont java, javac et jshell.
Répertoire bin d’une distribution Java, avec tous les binaires des outils dont java, javac et jshell

Pour le lancer il suffit donc normalement de saisir jshell dans une invite de commandes ou un shell, si vous avez un JDK correctement installé dont les binaires sont bien dans le PATH.

L’outil fonctionne de la même manière sur MacOs, Windows ou Linux, dans un shell de commandes. Je l’utilise pour ma part dans un environnement Windows.

Connaître la version de jshell

Pour connaitre la version de jshell, vous avez les options --version qui ne fait qu'afficher la version et --show-version qui affiche la version et lance jshell

Options pour voir la version de jshell
Options pour voir la version de jshell

Afficher de l’aide

Vous avez bien sûr les classiques options pour afficher les options de l’outil avec --help, -h ou -?, par exemple jshell -?.

Option de jshell pour afficher l’aide de l’outil
Option de jshell pour afficher l’aide de l’outil

Préciser un classpath

Il est possible de préciser des bibliothèques à inclure dans le classpath avec l’option --class-path <path>. Par exemple si je veux pouvoir utiliser des classes de la bibliothèque Apache Common Math, le jar correspondant étant dans le répertoire C:\tmp\javalib\ : je pourrais lancer jshell de la manière suivante

jshell --class-path C:\tmp\javalib\commons-math3-3.6.1.jar
Option de jshell pour renseigner le classpath à utiliser avec ici une seule bibliothèque
Option de jshell pour renseigner le classpath à utiliser
import org.apache.commons.math3.util.MathUtils;

MathUtils.PI_SQUARED;

Si vous souhaitez inclure plusieurs bibliothèques ou répertoires dans le classpath, il faut les renseigner les uns à la suite des autres avec le séparateur de path du système (; sous Windows et : sous Unix).

Ainsi si je souhaite importer Apache Common Math et Apache Common Lang, les jar correspondant étant dans le répertoire C:\tmp\javalib\ :

jshell --class-path C:\tmp\javalib\commons-math3-3.6.1.jar;C:\tmp\javalib\commons-lang3-3.12.0.jar
Option de jshell pour renseigner le classpath à utiliser avec ici 2 bibliothèques
Option de jshell pour renseigner le classpath à utiliser
import org.apache.commons.math3.util.MathUtils;
import org.apache.commons.lang3.RandomUtils;
MathUtils.PI_SQUARED;
RandomUtils.nextInt();

Imports et scripts de démarrage

Il est possible de préciser un script de démarrage avec l’option --startup <script démarrage>. Cela peut être un fichier local que vous avez écrit ou un des scripts prédéfinis que l'on précise par une valeur spécifique. On peut bien sûr associer cette option avec la précédente relative au classpath. Ainsi si je souhaite avoir un script qui importe MathUtils et RandomUtils dès le démarrage je peux procéder de la manière décrite ci-après.

J’ai un script my-import.jsh avec les lignes présentées ci-dessous sous C:\tmp\ :

import org.apache.commons.math3.util.MathUtils;
import org.apache.commons.lang3.RandomUtils;

Je lance jshell avec la ligne suivante, les jar d’Apache Common Math et d’Apache Common Lang étant dans le répertoire C:\tmp\javalib\ :

jshell --class-path C:\tmp\javalib\commons-math3-3.6.1.jar;C:\tmp\javalib\commons-lang3-3.12.0.jar --startup my-import.jsh

Une fois jshell lancé, si vous tapez la commande /imports, qui permet de visualiser les imports de la session — elle est détaillée plus loin — , vous pourrez vérifier que les classes sont bien importées.

Option de jshell pour exécuter des scripts au démarrage

Les valeurs pour les scripts prédéfinis sont les suivantes :

  • DEFAULT : importe les bibliothèques standards de Java les plus communément utilisées.
  • JAVASE : importe l'ensemble des bibliothèques standard de Java.
  • PRINTING : définit print, println et printf comme des fonctions directement utilisables dans jshell.

Ses scripts sont joués lors du démarrage mais également lors de redémarrages de session qui peuvent être provoquées par certaines commandes que nous verrons par la suite.

L’option DEFAULT est particulière car les imports que ce script fait sont effectués par défaut si on ne précise pas l’option --startup, comme vous pouvez le constater dans la copie d’écran suivante qui utilise à nouveau la commande /imports pour visualiser les imports.

Option de jshell pour exécuter des scripts au démarrage

On notera qu’il n’y a pas de différence aux niveaux des imports réalisés par défaut entre jshell et jshell --startup DEFAULT.

Par contre si on utilise l’option --startup et que l’on souhaite avoir les imports normalement effectués par DEFAULT, il faut penser à l’ajouter.

Option de jshell pour exécuter des scripts au démarrage

On voit que si on ne précise pas DEFAULT avec my-import.jsh aucuns des imports par défaut ne sont effectués.

Si on lance jshell avec l'option --startup PRINTING, on n'a plus les imports par défaut. Par contre, avec PRINTING on note qu'on peut utiliser directement print.

Option de jshell pour exécuter des scripts au démarrage

Pour effectuer le chargement de plusieurs scripts au démarrage on peut les mettre les uns à la suite des autres, séparés par des espaces (voir les exemples précédents) mais l’on peut également renseigner plusieurs fois l’option comme dans l’exemple ci-après.

Option de jshell pour exécuter des scripts au démarrage

Il faut noter qu’avec la commande open décrite plus loin, on peut également lancer ces scripts prédéfinis si on n’a pas pu ou pas voulu les lancer au démarrage de jshell.

Utiliser des fonctionnalités en preview dans le JDK

Si vous voulez pouvoir tester avec jshell les fonctionnalités en preview de votre JDK, il suffit de le lancer avec l'option --enable-preview : jshell --enable-preview.

Par exemple, avec la version de jshell que j’utilise (Java 17), je veux essayer le pattern matching dans les switchs (c’est en preview en Java 17) en exécutant le script switch-example.jsh suivant :

Gist pour switch-example.jsh
double getDoubleUsingSwitch(Object o) {
return switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0d;
};
}

System.out.println(getDoubleUsingSwitch("56.6"));
System.out.println(getDoubleUsingSwitch(5.4f));
System.out.println(getDoubleUsingSwitch(5));
System.out.println(getDoubleUsingSwitch(new Object()));
Option de jshell pour exécuter pour pouvoir utiliser les fonctionnalités en preview

Toutes les options ne sont pas sur la table

Je n’ai présenté qu’une partie des options qui me semblaient les plus pertinentes et utiles. Beaucoup d’autres options sont utilisables au lancement de jshell.

Commandes

Une fois dans jshell, vous pouvez saisir du code Java directement.

Les commandes de jshell (par exemple ouvrir un fichier) sont introduites par le caractère /.

Il y a des nombreuses commandes possibles pouvant être paramétrées avec des options. Comme pour les options de lancement, on se concentre ci-après sur les plus utiles (de mon point de vue) dans une utilisation quotidienne.

Sortir de jshell

Pour sortir de jshell, la commande est simplement /exit.

Obtenir de l’aide

/help pour afficher la liste des commandes et ce qui est nommé subjects (voir les copies d’écran) dans jshell.

Il est possible de passer à la suite de help un nom de commande (sans oublier le / devant) ou un nom de subject. Par exemple

  • /help /exit
  • /help intro

Complétion de commande

La complétion de commande fonctionne avec TAB après avoir commencé à saisir le caractère /.

Ouvrir un fichier

Pour ouvrir et faire exécuter un fichier de code java : /open <nom-fichier>.

Par exemple avec l’invite de commande depuis laquelle jshell a été ouverte positionnée dans le répertoire dans lequel le fichier perfectNumber.jsh se trouve.

/open perfectNumber.jsh

Exemple d’utilisation de la commande /open

Il est de bon ton d’utiliser l’extension jsh pour vos fichiers de scripts jshell mais ce n'est pas une obligation. Ils peuvent avoir l'extension java ou n'importe quelle autre valeur. A noter cependant que du code java valide pour jshell ne le sera pas forcément pour votre IDE ou votre éditeur de texte.

A toutes fins utiles, le script utilisé dans cet exemple.

Exemple de script Java à exécuter sous jshell

Vous pouvez également utiliser la commande /open pour ouvrir les mêmes scripts par défaut que ceux qu'on peut lancer au démarrage de jshell :

  • DEFAULT : importe les bibliothèques standards de Java les plus communément utilisées.
  • JAVASE : importer l'ensemble des bibliothèques standard de Java.
  • PRINTING : définit print, println et printf comme des fonctions directement utilisables dans jshell.
Exemple d’utilisation de la commande /open pour ouvrir les scripts prédéfinis

Lister les imports

Pour lister les imports la commande est /imports. Cela vous donne la liste des imports disponibles directement dans jshell.

Capture d’écran illustrant le fonctionnement de la commande /imports

Dans la copie d’écran, on voit l’effet de la commande suite au lancement sans options de jshell, avec les imports par défaut. Suite à l'ouverture du script perfectNumber.jsh qui réalise 2 imports supplémentaires, on voit apparaître ces derniers dans la liste suite à l'utilisation de la commande imports.

Lister les snippets

Un snippet est un morceau de code saisi, chargé et exécuté lors d’une session avec jshell. Il y a plusieurs opérations sur les snippets tel que les lister, les sauvegarder ou les supprimer. Ainsi il est possible de transformer du code que vous avez rédigé lors de votre session pour le consolider en un script exécutable par jshell.

Pour avoir la liste des snippets saisis :/list

Le code devant le snippet est un identifiant du snippet. Cet identifiant peut être utilisé dans certaines commandes pour le référencer comme/drop ou /save qui permettent respectivement de supprimer et de sauver des snippets.

Avec /list -all on liste tous les snippets y compris ceux qui ont échoués et ceux exécutés au lancement de jshell (comme les imports effectués par défaut que l'on voit dans la copie d'écran ci-dessus).

On notera que les identifiants des snippets exécutés au lancement et ceux en erreur, commence respectivement pas un s et un e.

Sauvegarder les snippets

On peut sauvegarder un ou des snippets en précisant un identifiant /save <id1> <nom fichier> ou une plage d'identifiants /save <id1>-<id2> <nom fichier>. Il faut alors l'utiliser en conjonction avec la commande/list.

Sauvegarde sélective de snippets

Le contenu du fichier obtenu dans l’exemple en copie d’écran est le suivant :

Script produit suite à la sauvegarde sélective de snippets

Il est également possible de sauvegarder l’historique des commandes avec les snippets avec save -history <nom fichier> ou tous les snippets avec save -all <nom fichier>

Capture d’écran illustrant la sauvegarde d’historique des commandes

L’exemple précédent donne respectivement les 2 fichiers suivants :

On notera qu’avec le /save -all on sauvegarde tous les snippets mais pas les commandes.

Lister les types, les variables et les méthodes

Il y a 3 commandes pour lister les types (classes, énumérations, interfaces, annotations), les variables et les méthodes définies dans la sessions jshell courante : /types, /vars et /methods. Pour chacune de ces 3 commandes vous pouvez utiliser l'option--all pour avoir également ce qui a été chargé au démarrage (entre autre chose).

Exemple d’utilisation des commandes /types, /vars et /methods

Manipuler les variables d’environnements de la session

La commande /env permet de visualiser et manipuler la configuration relative à l’environnement de la session, c’est-à-dire essentiellement les chemins à inclure pour récupérer les classes (class path) et les modules (module path).

Lancer sans options /env affiche les chemins définis pour les classes et les modules pour la session courante.

Copie d’écran illustrant le fonctionnement de la commande /env sans options
Copie d’écran illustrant le fonctionnement de la commande /env sans options

Ainsi, si je lance jshell sans préciser de class path ou de module path, /env ne retourne rien. Si je précise un class path au lancement, sa valeur est indiquée par /env.

Cette commande vous permet également de définir de nouvelles valeurs pour le class path ou le module path pour la session courante, sans avoir à recréer une nouvelle session.

Ainsi, si je lance une session jshell, sans donner de class path mais que je souhaite finalement préciser ce dernier en cours de session, je vais pouvoir le faire avec /env, dans l’exemple ci-après avec /env -class-path C:\tmp\javalib\commons-math3-3.6.1.jar.

Copie d’écran illustrant le fonctionnement de la commande /env pour modifier le classpath de la session
Copie d’écran illustrant le fonctionnement de la commande /env pour modifier le classpath de la session

Manipuler l’historique des commandes

On peut se déplacer dans l’historique des commandes d’une session avec les flèches haut et bas. La commande/history permet d'avoir la liste des commandes effectuées.

Vous pouvez rechercher dans l’historique avec la combinaison de touches Ctrl + R.

Intégration dans les outils de développement

jshell s’utilise bien sûr dans une invite de commande mais il y a des intégrations dans les IDE, les éditeurs de texte et même les outils de build.

IntelliJ IDEA fournit directement la possibilité d’ouvrir une session jshell liée au projet courant.

Outil JShell dans IntelliJ IDEA

Le classpath de la session jshell pourra être celui du projet courant.

Exécution d’un script jshell dans IntelliJ IDEA

Par contre les commandes jshell ne sont pas utilisables et j’ai pu constater que par exemple pour les méthodes définies lors d’une session, il faut les rendre static par rapport à une version faite directement pour jshell.

Il est agréable d’avoir le formatage, de l’auto-complétion avancée et la gestion des imports mais on ne peut pas ouvrir juste un script indépendant seul, il faut forcément utiliser l’outil dans le cadre d’un projet.

Eclipse ne propose pas directement l’intégration de jshell. Cependant il existe le plugin QuickShell qui offre plus de possibilités une fois installé que ce que propose IntelliJ IDEA : on peut ouvrir une session jshell similaire à ce qu’on a dans une invite de commande, il est possible de faire exécuter directement un bout de code dans jshellvia le menu contextuel. N’étant plus utilisateur d’Eclipse depuis pas mal de temps, je préfère vous renvoyer vers la page GitHub de QuickShell qui donne toutes les informations pertinentes.

Il y a également un support de jshell dans VS Code via un plugin, JShell Easy, qui interprète en direct le code saisi.

Plugin JShell Easy dans VScode

Enfin, il existe un plugin pour maven qui permet de lancer une session jshell via maven (avec un mvn jshell:run). L’intérêt ici est de pouvoir lancer cette session avec le classpath défini par les dépendances du POM (il existe également un plugin pour Gradle mais je ne l’ai pas testé).

Utilisation de jshell

jshell est très intéressant pour tester ou expérimenter un petit bout de code.

Par exemple pour mettre au point une expression un peu compliquée avec des streams ou une expression régulière, jshell pourra être un outil pertinent.

Pour apprendre la syntaxe de Java ou l’enseigner à quelqu’un, cela peut être intéressant pour s’éviter la lourdeur d’un IDE et de Java lui-même pour faire des choses simples : penser au code nécessaire et aux concepts à expliquer à un débutant en informatique pour afficher un “Hello World” en Java !

Et dans un cadre qui rejoint à la fois l’expérimentation et la pédagogie, je trouve que c’est parfois un outil intéressant pour montrer des exemples de code dans des billets de blog ou des articles.

En effet, il est assez aisé de faire un script auto-porteur qui tiendra en relativement peu de lignes et qui sera facile à tester pour le lecteur avec un outil disponible dans le JDK. Cela ne remplace pas des exemples plus complets intégrés dans un vrai projet Java mais cela apporte un complément qui permet de mettre le focus sur ce qui est essentiel.

Conclusion

C’était une présentation non-exhaustive (j’ai laissé certaines commandes et options de côté) d’un outil du JDK méconnu mais utile.

C’est un outil intéressant pour expérimenter rapidement une idée ou tester un petit bout de code ; on aurait tord de se priver de l’utiliser.

Pour aller plus loin, la documentation d’Oracle est déjà très complète :

Il y a également un chapitre complet dans l’exhaustif et indispensable “Développons en Java” de Jean-Michel Doudoux.

Remerciement

Je remercie Baptiste, Damien, Thomas et Farid pour leur relecture attentive et leurs remarques.

Note

Une première version de ce billet a été publiée sur mon blog personnel.

--

--

Christophe Vaudry
norsys-octogone

Developer working for Norsys. Programming languages explorer. Know nothing.