Что такое JShell и как им пользоваться?

User Rating: 5 / 5

Доброго времени суток, друзья. В сегодняшней статье мы с вами познакомимся с таким интересным инструментом, как JShell (или Java Shell tool).

Итак, что такое JShell и как им пользоваться?

JShell - это специальный интерактивный инструмент для изучения языка программирования Java и прототипирования Java кода. Инструмент был включен в состав JDK, начиная с версии 9. Согласно странице официальной документации, JShell является инструментом типа REPL (Read-Evaluate-Print Loop tool), т.е. фактически он реализует парадигму цикла "прочитать-вычислить-распечатать". JShell доступен из командной строки и позволяет вычислять объявления, инструкции и выражения как только они вводятся в консоль и мгновенно отображать результаты вычислений.

Для чего имеет смысл использовать JShell?

Когда вы используете JShell, то можете вводить элементы программы по одному, сразу видеть результат и вносить коррективы по мере необходимости. Обычно процесс разработки программы Java включает в себя написание самой программы, её компиляцию и исправление ошибок, запуск программы и выявление того, что в ней не работает с последующим редактированием не работающих частей кода и повторением процесса.

Инструмент JShell помогает тестировать и пробовать код, легко исследовать различные опции при разработке программы. Можно тестировать отдельные инструкции, различные вариации метода и экспериментировать с незнакомыми API в пределах сессии работы в JShell. Важно при этом отметить, что JShell не является полноценной заменой интегрированной среды разработки (IDE). Вы можете вставлять код в JShell, пока вы разрабатываете, а затем вставлять работающий код из JShell в ваш редактор программы или IDE, которую вы используете.

Как запустить JShell? Запуск инструмента в обычном и подробном режимах

JShell работает из командной строки, поэтому для его запуска нужно набрать команду jshell в командной строке (в Windows для запуска командной строки откройте меню "Пуск", наберите cmd и нажмите Enter), и если всё было сделано верно, вы увидите примерно следующее:

C:\Users\1>jshell
|  Welcome to JShell -- Version 11
|  For an introduction type: /help intro

jshell>

Если же вы увидели ошибку, говорящую о том, что команда jshell не найдена, то проверьте следующее:

  • У вас должен быть установлен JDK версии не ниже 9-й
  • В вашей переменной окружения Path должен присутствовать путь до установленного JDK и директории /bin внутри него. Например, в моём случае у меня в переменной Path присутствует путь C:\Program Files\Java\jdk-11\bin\

Когда вы запускаете команду jshell в командной строке, то вы запускаете инструмент JShell в обычном режиме, однако существует также возможность запустить инструмент в подробном режиме (verbose mode).

Для этого нужно указать ключ -v после команды jshell:

C:\Users\1>jshell -v
|  Welcome to JShell -- Version 11
|  For an introduction type: /help intro

jshell>

Выход из JShell

Чтобы завершить работу в JShell, введите команду /exit:

C:\Users\1>jshell -v
|  Welcome to JShell -- Version 11
|  For an introduction type: /help intro

jshell> /exit
|  Goodbye

C:\Users\1>

Далее по тексту статьи подразумевается, что jshell запущена в подробном режиме с ключом -v. На первых этапах знакомства с инструментом этот режим даёт больше деталей в консоли о том, что происходит при исполнении инструкций. Когда вы станете более опытным пользователем, то сможете всегда переключиться на нормальный режим с менее детальным выводом сообщений.

Пробуем JShell в действии. Работа с переменными

Давайте напишем несколько команд на языке Java, чтобы лучше познакомиться с возможностями JShell.

Итак, сначала давайте попробуем ввести следующую инструкцию с инициализацией целочисленной переменной x:

jshell> int x = 5;
x ==> 5
|  created variable x : int

jshell>

Обратите внимание на то, что сразу нам показывается результат инструкции:

x ==> 5

который можно читать следующим образом: переменная x имеет значение 5. Также, ввиду того, что мы находимся в подробном режиме, то нам выводится описание того, что именно произошло:

| created variable x : int

т.е. была создана переменная x с типом int. Обратите внимание на то, что информационные сообщения начинаются с вертикальной черты, также выводится как тип, так и имя созданной переменной.

Попробуем ввести ещё одну переменную y и присвоить ей значение 7:

jshell> int y = 7;
y ==> 7
|  created variable y : int

jshell>

Видим, что аналогичным образом вывелось значение переменной и информационное сообщение, говорящее нам о том, что была создана переменная y с типом int.

Теперь мы можем, к примеру, сложить две переменные и увидеть результат:

jshell> x + y;
$3 ==> 12
|  created scratch variable $3 : int

jshell>

Здесь происходит нечто интересное - мы просто написали выражение x + y, но в явном виде не присваивали результат этого сложения в отдельную переменную. При этом JShell создал для нас временную переменную (scratch variable$3, куда записал результат сложения.

Теперь всегда, когда нам потребуется обратиться к ней и увидеть её содержимое, мы можем написать имя этой временной переменной:

jshell> $3
$3 ==> 12
|  value of $3 : int

jshell>

Попробуем теперь сложить эту временную переменную с каким-то числом:

jshell> $3 + 1000
$5 ==> 1012
|  created scratch variable $5 : int

jshell>

Как видим, результат этого сложения записался в новую временную переменную $5 и он равен 1012.

Чтобы увидеть все доступные переменные - как явно объявленные, так и временные, можно воспользоваться командой /vars. Она отображает имя, тип и значение переменных, которые были введены. Если команда используется без дополнительных опций, то она отобразит все активные переменные:

jshell> /vars
|    int x = 5
|    int y = 7
|    int $3 = 12
|    int $5 = 1012

jshell>

В опциях можно, к примеру, указать имена конкретных переменных, которые нас интересуют, чтобы быстро посмотреть их значения:

jshell> /vars x y
|    int x = 5
|    int y = 7

jshell>

Полный список опций вы найдете внизу статьи, где я привёл ссылки на официальную документацию (описание команды JShell и пользовательский гайд по JShell).

Вывод справки по командам

Чтобы вывести справку по доступным командам jshell, используем либо /help, либо /? - эти две команды работают одинаково:

jshell> /?
|  Type a Java language expression, statement, or declaration.
|  Or type one of the following commands:
|  /list [<name or id>|-all|-start]
|       list the source you have typed
|  /edit <name or id>
|       edit a source entry
|  /drop <name or id>
|       delete a source entry
|  /save [-all|-history|-start] <file>
|       Save snippet source to a file
|  /open <file>
|       open a file as source input
|  /vars [<name or id>|-all|-start]
|       list the declared variables and their values
|  /methods [<name or id>|-all|-start]
|       list the declared methods and their signatures
|  /types [<name or id>|-all|-start]
|       list the type declarations
|  /imports
|       list the imported items
|  /exit [<integer-expression-snippet>]
|       exit the jshell tool
|  /env [-class-path <path>] [-module-path <path>] [-add-modules <modules>] ...
|       view or change the evaluation context
|  /reset [-class-path <path>] [-module-path <path>] [-add-modules <modules>]...
|       reset the jshell tool
|  /reload [-restore] [-quiet] [-class-path <path>] [-module-path <path>]...
|       reset and replay relevant history -- current or previous (-restore)
|  /history [-all]
|       history of what you have typed
|  /help [<command>|<subject>]
|       get information about using the jshell tool
|  /set editor|start|feedback|mode|prompt|truncation|format ...
|       set configuration information
|  /? [<command>|<subject>]
|       get information about using the jshell tool
|  /!
|       rerun last snippet -- see /help rerun
|  /<id>
|       rerun snippets by ID or ID range -- see /help rerun
|  /-<n>
|       rerun n-th previous snippet -- see /help rerun
|
|  For more information type '/help' followed by the name of a
|  command or a subject.
|  For example '/help /list' or '/help intro'.
|
|  Subjects:
|
|  intro
|       an introduction to the jshell tool
|  id
|       a description of snippet IDs and how use them
|  shortcuts
|       a description of keystrokes for snippet and command completion,
|       information access, and automatic code generation
|  context
|       a description of the evaluation context options for /env /reload and /reset
|  rerun
|       a description of ways to re-evaluate previously entered snippets

Сниппеты в JShell

Сниппеты (Snippets) кода на Java вводятся в JShell и мгновенно вычисляются. При этом на консоль выдаётся обратная связь о результатах, выполненных действиях и любых произошедших ошибках.

Например, можно быстро сложить два числа:

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

jshell>

Можно создать сниппет для метода, который будет складывать два целых числа:

jshell> int addTwoNumbers(int num1, int num2) {
   ...> return num1 + num2;
   ...> }
|  created method addTwoNumbers(int,int)

jshell>

Обратите внимание на то, как при переводе на новую строку после открывающей фигурной скобки jshell предлагает продолжать ввод текста для нашего метода. Это очень удобно, особенно учитывая, что как только мы закрывающей фигурной скобкой закрываем тело метода, то мгновенно получаем обратную связь - был создан метод с именем addTwoNumbers(int, int).

Давайте теперь вызовем наш метод, используя ранее объявленные переменные x и y:

jshell> addTwoNumbers(x, y)
$9 ==> 12
|  created scratch variable $9 : int

jshell>

Также можем передать в метод и временные переменные, созданные ранее при наших вычислениях:

jshell> addTwoNumbers($3, $5)
$10 ==> 1024
|  created scratch variable $10 : int

jshell>

Если вы подзабыли какие переменные объявляли ранее, а какие временные переменные были созданы в текущей сессии работы с jshell, используйте снова команду /vars.

Изменения определений

Во время работы с jshell вы можете захотеть изменить определение какой-то переменной или метода, в этом случае определение легко изменяется - для этого достаточно просто ввести новое.

Давайте изменим наше определение для метода addTwoNumbers, что задали ранее. Предположим, мы хотим сначала умножить значение каждого из двух входных параметров на 2, а затем уже сложить результаты. Изменим определение метода, введя следующее:

jshell> int addTwoNumbers(int num1, int num2) {
   ...> return num1 * 2 + num2 * 2;
   ...> }
|  modified method addTwoNumbers(int,int)
|    update overwrote method addTwoNumbers(int,int)

jshell>

Видим обратную связь от JShell: выдалось сообщение, что был переписан метод addTwoNumbers(int, int).

Проверим его в действии снова на переменных x и y:

jshell> addTwoNumbers(x, y)
$12 ==> 24
|  created scratch variable $12 : int

jshell>

Если вы уже забыли, чему у нас равны x и y, я напомню (а также напомнит команда /vars):

x = 5, y = 7.

Итак, 5 * 2 + 7 * 2 = 10 + 14 = 24.

Как видим, переопределение метода сработало, и он выдаёт корректный результат.

Вывод списка всех методов

Чтобы вывести все определённые методы, используйте команду /methods:

jshell> /methods
|    int addTwoNumbers(int,int)

jshell>

Поскольку у нас был объявлен лишь один метод, то мы и увидели его в выводе команды.

Давайте зададим ещё один, который будет конкатенировать две входных строки:

jshell> String concatStr(String s1, String s2) {
   ...> return s1 + s2;
   ...> }
|  created method concatStr(String,String)

jshell>

И снова запустим команду /methods:

jshell> /methods
|    int addTwoNumbers(int,int)
|    String concatStr(String,String)

jshell>

В этот раз мы видим оба метода, которыми мы можем оперировать в текущей сессии.

Ссылки на будущие определения (Forward References)

Я не знал, как лучше и правильнее перевести термин forward references, который используется в официальной документации, поэтому решил перевести это как "ссылки на будущие определения".

Суть таких ссылок мы сейчас с вами рассмотрим, это очень полезная фича, которую предоставляет JShell.

Итак, JShell может работать с определениями, которые ссылаются на методы, переменные или классы, которые пока ещё не определены. Это сделано для поддержки исследовательского программирования и ввиду того, что этого требуют некоторые формы программирования.

Посмотрим на примере. Мы хотим определить метод addTwoTransformedNumbers(int, int), который будет использовать в своём теле какой-то другой метод, скажем, с именем transform, который мы хотим применить к каждому из параметров, а результаты сложить между собой и вернуть в качестве результата:

jshell> int addTwoTransformedNumbers(int num1, int num2) {
   ...> return transform(num1) + transform(num2);
   ...> }
|  created method addTwoTransformedNumbers(int,int), however, it cannot be invoked until method transform(int) is declared

jshell>

Как видим, в JShell вполне успешно можно создать подобный метод со ссылкой на будущее определение: метод был создан, однако он не может быть вызван, пока не будет объявлен метод transform(int).

Что будет если прямо сейчас вызвать метод addTwoTransformedNumbers? Пробуем:

jshell> addTwoTransformedNumbers(2, x)
|  attempted to call method addTwoTransformedNumbers(int,int) which cannot be invoked until method transform(int) is declared

jshell>

Мы попытались вызвать метод, передав в него константу и нашу ранее объявленную переменную x, но ничего не вышло: мы получили сообщение о том, что произведена попытка вызвать метод, который не может быть вызван до тех пор, пока transform(int) не будет объявлен.

Давайте объявим метод transform(int). Пусть он умножает свой аргумент на 10 и возвращает тип int:

jshell> int transform(int inputNumber) {
   ...> return 10 * inputNumber;
   ...> }
|  created method transform(int)
|    update modified method addTwoTransformedNumbers(int,int)

jshell>

Смотрите внимательно на сообщения вывода: можно заметить, что помимо создания нового метода transform также был изменён и метод addTwoTransformedNumbers!

Давайте ещё раз попробуем его вызвать:

jshell> addTwoTransformedNumbers(2, x)
$17 ==> 70
|  created scratch variable $17 : int

jshell>

Итак, в этот раз всё успешно получилось! Что в итоге произошло:

1) мы передали в метод addTwoTransformedNumbers число 2 и переменную x (а она равна у нас 5)

2) метод addTwoTransformedNumbers возвращает результат: return transform(2) + transform(5)

3) т.к. метод transform просто умножает свой аргумент на 10, то мы возвращаем следующий результат: 2 * 10 + 5 * 10 = 20 + 50 = 70

Использование табуляции для быстрого ввода сниппетов

Вы можете использовать клавишу <TAB> для более быстрого ввода сниппетов в консоли jshell. Просто введите часть имени сниппета и нажмите <TAB> для автоматического ввода полного имени.

jshell> "hello world".startsWith(<TAB>
concatStr(

Signatures:
boolean String.startsWith(String prefix, int toffset)
boolean String.startsWith(String prefix)

<press tab again to see documentation>

jshell> "hello world".startsWith(

В этом примере нам подсказано два возможных варианта вызова метода .startsWith() доступного для строк.

Интересной особенностью jshell является то, что повторное нажатие клавиши <TAB> приведёт к тому, что будет отображена документация на первый из методов (см. подсказку в выводе команды - <press tab again to see documentation>). А ещё одно нажатие клавиши <TAB> покажет документацию на 2-й вариант метода.

Создание классов

Точно так же, как и создание переменных и методов, вы можете создавать классы через консоль jshell:

jshell> class Foo {
   ...> private int x;
   ...> public Foo(int x) {
   ...> this.x = x;
   ...> }
   ...> public int getX() {
   ...> return x;
   ...> }
   ...> public void setX(int x) {
   ...> this.x = x;
   ...> }
   ...> }
|  created class Foo

Здесь мы создали простой класс Foo с одним приватным полем x, публичным конструктором с параметром и геттер и сеттер для поля x

Теперь мы можем создавать объекты класса Foo:

jshell> Foo myFoo = new Foo(100);
myFoo ==> Foo@2758fe70
|  created variable myFoo : Foo

jshell>

и обращаться к ним:

jshell> myFoo.getX()
$24 ==> 100
|  created scratch variable $24 : int

jshell>

Запуск редактора

Также ещё одной интересной командой является команда /edit, и я рекомендую её попробовать. Она открывает редактор, который позволит более удобно поменять текст сниппетов. Если не было задано опций для этой команды, то открывается редактор с активными сниппетами. Про доступные опции рекомендую почитать подробнее по ссылкам на документацию ниже. 

На этом, пожалуй, всё. Другие полезные возможности вы сможете найти в официальной документации по ссылкам ниже. Делитесь своими мыслями относительно данной полезной утилиты, доступной по умолчанию в пакете JDK, а также пишите, приходилось ли вам её использовать в работе?

Спасибо за внимание и удачи.

Ссылки по теме

Java Shell User's Guide

The JShell Command

Яндекс.Метрика