Chapter 20. JShell: the Java Shell

JShell, originally called Project Kulla, is an interactive command-line read-eval-print-loop (REPL) tool introduced in the Java 9 SDK. Similar in functionality to such interpreters as Python’s ipython and Haskell’s ghci, JShell allows users to evaluate and test fragments of code in real time without the trouble of creating a test project or a class housing a main function.

The code in this chapter was tested against JShell version 9-ea.

Getting Started

JShell can be launched from the menu of the NetBeans IDE (Tools→Java Platform Shell), from the Windows command line by running jshell.exe from the /bin/ directory of your JDK installation, or in POSIX environments with the jshell command.

When the environment has loaded, you will be greeted with a prompt:

|  Welcome to JShell -- Version 9-ea
|  For an introduction type: /help intro


jshell>

From here, you will be able to enter, execute, or modify code snippets, or interact with the JShell environment through its built-in commands.

Snippets

JShell operates upon units called snippets, which are code fragments entered by the user at the jshell> prompt. Each snippet must take a form defined in the JLS, as summarized in Table 20-1:

Table 20-1. Permitted snippet forms
Java Language Specification Production Example

Primary

10 / 2

Statement

if (value == null) { numWidgets = 0; }

ClassDeclaration

class Foo { }

MethodDeclaration

void sayHello () { System.out.println(“Hello”); }

FieldDeclaration

boolean isAnchovyLover = true;

InterfaceDeclaration

interface eventHandler { void onThisEvent(); }

ImportDeclaration

import java.math.BigInteger;

Modifiers

JShell handles modifiers differently than does standard compiled Java. Most notably, it prohibits the use of several in top-level declarations (i.e., in the main JShell “sandbox” and outside of the scope of a class/method declaration or other nested context). The following example warns the user when the inappropriate use of the private modifier is attempted:

jshell> private double airPressure
|  Warning:
|  Modifier 'private'  not permitted in top-level
   declarations, ignored
|  private double airPressure;
|  ^-----^
airPressure ==> 0.0

jshell> class AirData { private double airPressure; }
|  created class AirData

Table 20-2 shows a summary of JShell’s modifier policies.

Table 20-2. JShell modifier rules
Modifier Rule

private, protected, public, final, static

Ignored with warning if top-level declaration

abstract, default

Usable only in class declarations

default, synchronized

Prohibited in top-level declarations

Flow Control Statements

Similarly, the flow control statements break, continue, and return are disallowed at the top level, as they have no relevant meaning in that context.

Package Declarations

Package declarations are not allowed in JShell, as all JShell code is placed in the transient package jshell.

Using JShell

As mentioned in “Getting Started”, your interaction with JShell will primarily consist of entering, manipulating, and executing snippets. The following sections provide detail on working with each of the major snippet varieties, as well as saving and loading code and input histories and restoring and persisting JShell’s state.

Primary Expressions

JShell will immediately evaluate and/or execute any primary expressions entered via the prompt:

jshell> 256 / 8
$1 ==> 32

jshell> true || false
$2 ==> true

jshell> 97 % 2
$3 ==> 1

jshell> System.out.println("Hello, Dave. Shall we continue the game?")
Hello, Dave. Shall we continue the game?

jshell> StringBuilder sb = new StringBuilder("HAL")
sb ==> HAL

jshell> sb.append(" 9000")
$4 ==> HAL 9000

Notice that JShell will append missing semicolons to the ends of expressions and statements. Semicolons are, however, required as usual when declaring methods, classes, and other code contained in blocks.

Dependencies

The command /imports returns a list of all libraries currently imported into the workspace:

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

The results represent the libraries that JShell imports into each new workspace by default. Additional libraries can be imported via the import command:

jshell> import java.lang.StringBuilder

Statements and Code Blocks

Like primary expressions, snippets representing statements are immediately executed upon entry:

jshell> double[] tempKelvin = {373.16, 200.19, 0.0}
tempKelvin ==> double[3] { 373.16, 200.19, 0.0 }

When a statement contains one or more blocks of code, the JShell prompt becomes the new-line prompt (…>) upon the first carriage return press and continues reading the snippet line by line until the highest-level block is terminated:

jshell> import java.text.DecimalFormat

jshell> DecimalFormat df = new DecimalFormat("#.#");
df ==> java.text.DecimalFormat@674dc

jshell> double[] tempFahrenheit = {30.8, 77.0, 29.3, 60.2 }
tempFahrenheit ==> double[5] { 30.8, 77.0, 29.3, 60.2 }

jshell> for (double temp : tempFahrenheit) {
   ...> double tempCelsius = ((temp - 32)*5/9);
   ...> System.out.println(temp + " degrees F is equal to "
   ...> + df.format(tempCelsius) + " degrees C.
");
   ...> }
30.8 degrees F is equal to -0.7 degrees C.
77.0 degrees F is equal to 25 degrees C.
29.3 degrees F is equal to -1.5 degrees C.
60.2 degrees F is equal to 15.7 degrees C.

If a code block contains a compile-time error such as a syntax error, the snippet will neither be created nor executed and must be re-entered. Although the up arrow key can be used at the prompt to scroll up through previous commands in the line buffer, this still can be a tedious process. Take care, then, to input large code blocks carefully when using the command line.

The command /! can be used to re-execute the snippet that was last run. Similarly, the /-<n> command will execute the prior snippet relative to the number supplied:

jshell> System.out.println("Hello");
Hello
jshell> System.out.println("World");
World
jshell> /!
System.out.println("World");
World
jshell> /-3
System.out.println("Hello");
Hello

Method and Class Declarations

Methods are declared in JShell in the same way as any other statements or code blocks, and may be invoked from the command line:

jshell> double KELVIN = 273.16
KELVIN ==> 273.16

jshell> double DRY_AIR_GAS_CONSTANT = 287.058
DRY_AIR_GAS_CONSTANT ==> 287.058

jshell> double getDryAirDensity(double temperature,
   ...>    double atmosphericPressure) {
   ...> // convert from hPa to Pa
   ...> double airDensity = atmosphericPressure * 100
   ...>   / (DRY_AIR_GAS_CONSTANT
   ...>   * (temperature + KELVIN));
   ...> return airDensity;
   ...> }
|  created method getDryAirDensity(double,double)

jshell> double todaysAirDensity =
   ...> getDryAirDensity(15, 1013.25)
todaysAirDensity ==> 1.2249356158607942

The command /methods returns a list of all methods currently residing in the workspace, as well as their signatures:

jshell> /methods
|    double getDryAirDensity (double,double)

The process for declaring classes is the same. In the following example, we wrap the air density calculator code in a utility class to apply the static final modifiers that will make our constants behave as constants:

jshell> class AirDensityUtils {
  ...> private static final double KELVIN = 273.16;
  ...> private static final double
  ...>   DRY_AIR_GAS_CONSTANT = 287.058;
  ...>
  ...> double getDryAirDensity(double temperature,
  ...>   double atmosphericPressure) {
  ...> // convert from hPa to Pa
  ...> double airDensity = atmosphericPressure * 100
  ...> / (DRY_AIR_GAS_CONSTANT
  ...> * (temperature + KELVIN));
  ...> return airDensity;
  ...> }
  ...> }
|  created class AirDensityUtils

The methods and members of the class can be accessed from the command line via standard Java dot notation. Although AirDensityUtils is a utility class, it cannot be referenced from a static context, as the static modifier is not allowed in top-level declarations, and so must be instantiated:

jshell> new AirDensityUtils().
  ...>    getDryAirDensity(15, 1013.25)
$5 ==> 1.2249356158607942

Other types, such as interfaces and enums, are also declared this way. The /types command will return a list of all types currently residing in the workspace:

jshell> interface EventHandler { void onWeatherDataReceived(); }
|  created interface EventHandler

jshell> enum WeatherCondition { RAIN, SNOW, HAIL }
|  created enum WeatherCondition

jshell> /types
|    class AirDenstityUtils
|    interface EventHandler
|    enum WeatherCondition

Viewing, Deleting, and Modifying Snippets

Once they are defined, snippets can be easily viewed, deleted, and modified. The /list command displays a list of all current snippet code, along with corresponding identification numbers:

jshell> /list

   1 : double KELVIN = 273.16;
   2 : double DRY_AIR_GAS_CONSTANT = 287.058;
   3 : double getDryAirDensity(double temperature,
         double atmPressure) {
         // convert from hPa to Pa
         double airDensity = atmoPressure * 100
         / (DRY_AIR_GAS_CONSTANT
         * (temperature + KELVIN));
       return airDensity;
       }
   4 : class AirDensityUtils {
       private static final double
         KELVIN = 273.16;
       private static final double
         DRY_AIR_GAS_CONSTANT = 287.058;

       double getDryAirDensity(double temperature,
         double atmPressure) {
         // convert from hPa to Pa
         double airDensity = atmPressure * 100
         / (DRY_AIR_GAS_CONSTANT *
         (temperature + KELVIN));
         return airDensity;
       }
       }

Snippets may be referenced in JShell commands either by name or by identification number. In the previous example, we made the two top-level pseudoconstants DRY_AIR_GAS_CONSTANT and KELVIN superfluous when we wrapped them and getDryAirDensity(double, double) in a class, so we will delete them by using the /drop command:

jshell> /drop KELVIN
|  dropped variable KELVIN

jshell> /drop 2
|  dropped variable DRY_AIR_GAS_CONSTANT

Modification or replacement of previously defined snippets is easy, as well. The first method by which to perform this action is simply to overwrite the original:

jshell> double getDryAirDensity(double temperature,
   ...>   double atmPressure) {
   ...>   // We don't need this method anymore,
   ...>   // but let's replace it anyway!
   ...> }
|  replaced method getDryAirDensity(double,double)

This is not a terribly practical solution for cases involving large code fragments or only minor adjustments. Fortunately, JShell also allows snippet code to be modified in an external editor via /edit <name> or /edit <id>.

images/DefaultEditPad.png
Figure 20-1. JShell default edit pad

In Figure 20-1, AirDensityUtils has been opened for editing in the default JShell edit pad. However, the text editor that JShell launches for this task may be specified using /set editor <_command_>, where +command+ is the operating system-dependent command to launch one’s text editor of choice. For example, in Linux, /set editor vim or /set editor emacs.

Regardless of which method one employs to modify a snippet, any snippets that refer to or depend upon the snippet being modified will not be affected by the change.

Saving, Loading, and State

The command /save <_file_> will save the source of all current active snippets to the designated filename. Applying the -⁠all flag will save all source code entered during the current session, including overwritten and rejected snippet code; applying the -history flag will save all snippet code and commands in the order in which they were entered.

Conversely, /open <_file_> will load the contents of the specified file as JShell input. Be aware that the file will not successfully load if it contains a package declaration.

JShell’s state may also be reset or restored while the session is active. The /reset command resets JShell’s state, clearing all entered code, restarting the execution state, and re-executing any startup code.

The /reload command, on the other hand, will reset JShell’s code and execution state and replay all valid snippet entries and commands. The replay will commence from the start of the session or from the last /reset or /reload, whichever happened most recently. Additionally, /reload -restore will restore JShell’s state from the previous session if used at startup.

JShell can be instructed to load snippets automatically after a /reset or /reload with the command /set start <_file_>, where file is a saved collection of snippet code. Further, subsequently using the command /retain start will cause the code to load each time JShell starts. This can be a useful feature for those working with the same set of methods and classes from session to session.

JShell Features

JShell sports a number of conveniences from other shell scripting and interpreter environments, as well as characteristics that set it apart from traditional compiled Java. Notable among these are scratch variables, tab smart-complete, forward referencing, leniency in checked exception handling, and its treatment of top-level variables.

Scratch Variables

The return value of a stand-alone primary expression or method invocation is stored in a scratch variable, which is prefixed with ($) and which is accessible from within the JShell environment:

jshell> 21 + 20
$6 ==> 41

jshell> $6 + 1
$7 ==> 42

jshell> "The meaning of life is " + $7
$8 ==> "The meaning of life is 42"

jshell> $8.getClass().getName()
$9 ==> "java.lang.String"

In order to see the return type of a statement without having to invoke getClass(), the user may set JShell’s feedback mode to verbose:

jshell> /set feedback verbose
|  Feedback mode: verbose

jshell> 7.0 % 2
$10 ==> 1.0
|  created scratch variable $10 : double

Tab Auto-Complete

The JShell environment includes one of the more convenient features of most modern command-line interpreters and shells: tab auto-completion. When the user presses the Tab key, JShell automatically completes partially typed variable, snippet, or object names.

In ambiguous cases, JShell presents the user with a list of possibilities. In the following example, the user presses the Tab key after typing temp, but there are currently three variables in the environment beginning with those characters:

shell> temp
tempCelsius      tempFahrenheit   tempKelvin

Forward Referencing

JShell allows for forward referencing in snippet definitions. That is, one may define a method that references other methods, classes, or variables that have not yet been defined. However, any undefined items must be defined before the method may be invoked or referenced:

jshell> void getDryAirDensity(
   ...>   MeasurementSystem unit) {
   ...> temperature = x;
   ...> pressure = y;
   ...> adjustUnits(x, y, unit);
   ...> // calculation code
   ...> }
|  created method
getDryAirDensity(MeasurementSystem
), however, it cannot be referenced until
class Measure mentSystem, variable
temperature, variable pressure, and
variable y are declared

Undefined classes may not be used as return types in method declarations, nor may any members, methods, or constructors of undefined classes be referenced.

Checked Exceptions

If a a single, stand-alone statement invokes a method that throws a checked exception, JShell will automatically provide the exception handling behind the scenes without any additional input from the user:

jshell> BufferedReader bReader = new BufferedReader(
   ...>   new FileReader("message.txt"))
bReader ==> java.io.BufferedReader@1e3c938

jshell> String txtLine;
txtLine ==> null

jshell> while ((txtLine = bReader.readLine()) != null)
   ...> { System.out.println(txtLine); }
I don't like macaroni cheese.  And I don't like
scrambled eggs.  And I don't like cocoa.

jshell> bReader.close()

In the preceding example, three file I/O operations are successfully performed without any IOException handling. However, when a snippet is a method or class declaration (i.e., it does not constitute one single, discrete statement), its code must handle any thrown exceptions as usual:

jshell> void displayMessage() {
   ...> BufferedReader bReader = new BufferedReader(
   ...>   new FileReader("message.txt"));
   ...> String txtLine;
   ...> while ((txtLine = bReader.readLine()) != null)
   ...>   { System.out.println(txtLine); }
   ...> bReader.close();
   ...> }
|  Error:
|  unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown
|  BufferedReader bReader = new BufferedReader(new FileReader("message.txt"));
|
|  Error:
|  unreported exception java.io.IOException; must be caught or declared to be thrown
|  while ((textLine = bReader.readLine()) != null)
{ System.out.println(textLine); }
|
|  Error:
|  unreported exception java.io.IOException; must be caught or declared to be thrown
|  bReader.close();
|

Hierarchy and Scope

An interesting feature of the JShell environment is that variables, methods, and classes declared at the top level are accessible from any scope in the JShell hierarchy.

This is due to the fact that the JShell interpreter wraps snippets within a synthetic class in order to make them comprehensible to the Java compiler, which only recognizes import statements and class declarations at the top level.

Specifically, top-level JShell variable, method, and class declarations are made static members of this synthetic class, while statements and primary expressions are enclosed in synthetic methods and then added to it. Import statements, being a recognized top-level construct, are placed unmodified at the top of the synthetic class.

The following example’s method and class both read and modify the top-level double variable pressure from within their respective scopes:

jshell> double pressure = 30.47
pressure ==> 30.47

jshell> void convertPressureinHgTohPa() {
   ...> pressure = pressure * 33.86389;
   ...> }
|  created method convertPressureinHgTohPa()

jshell> convertPressureinHgTohPa()

jshell> pressure
pressure ==> 1031.8327282999999

jshell> class WeatherStation {
   ...> double mAirPressure;
   ...> public WeatherStation() {
   ...> mAirPressure = pressure;
   ...> }
   ...> }
|  created class WeatherStation

jshell> WeatherStation ws = new WeatherStation()
ws ==> WeatherStation@b1ffe6

jshell> ws.mAirPressure
$11 ==> 1031.8327282999999

Such an example is not likely to win any accolades for programming best practices, but as JShell is an excellent playground for experimentation and quick, informal code testing, some may find this quirk useful.

Summary of JShell Commands

Table 20-3 shows a list of all commands available in the JShell environment. It may be accessed at any time from within JShell via the /help command.

Table 20-3. JShell commands
Command Description

/list [<name or id>|-all|-start]

Lists source code entered into JShell

/edit <name or id>

Edits source entry corresponding with name or ID

/drop <name or id>

Deletes source entry corresponding with name or ID

/save [-all|-history|-start] <file>

Saves specified snippets and/or commands to file

/open <file>

Opens file as source input

/vars [<name or id>|-all|-start]

Lists declared variables and corresponding values

/methods [<name or id>|-all|-start]

Lists declared methods and corresponding signatures

/types [<name or id>|-all|-start]

Lists declared classes, interfaces, and enums

/imports

Lists current active JShell imports

/exit

Exit JShell without saving

/reset

Resets JShell’s state

/reload [-restore] [-quiet]

Resets JShell state and replays history since JShell start or most recent /reset or /reload command

/history

Displays history of all snippets and commands entered since JShell was started

/help [<command>|<subject>]

Displays list of JShell commands and help subjects or further information on specified command or subject

/set editor|start|feedback|mode|prompt|truncation|format

Sets JShell configuration options

/retain editor|start|feedback|mode

Retains settings for use in subsequent JShell sessions

/? [<command>|<subject>]

Identical to /help

/!

Re-runs last snippet

/<id>

Re-runs a snippet referenced by ID

/-<n>

Re-runs nth previous snippet

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.39.142