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
.
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.
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:
Java Language Specification Production | Example |
---|---|
Primary |
|
Statement |
|
ClassDeclaration |
|
MethodDeclaration |
|
FieldDeclaration |
|
InterfaceDeclaration |
|
ImportDeclaration |
|
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.
Modifier | Rule |
---|---|
|
Ignored with warning if top-level declaration |
|
Usable only in class declarations |
|
Prohibited in top-level declarations |
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.
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.
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
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
@
674
dc
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
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
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>
.
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.
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 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.
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
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
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.
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
@
1
e3c938
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
();
|
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.
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.
Command | Description |
---|---|
|
Lists source code entered into JShell |
|
Edits source entry corresponding with name or ID |
|
Deletes source entry corresponding with name or ID |
|
Saves specified snippets and/or commands to file |
|
Opens file as source input |
|
Lists declared variables and corresponding values |
|
Lists declared methods and corresponding signatures |
|
Lists declared classes, interfaces, and enums |
|
Lists current active JShell imports |
|
Exit JShell without saving |
|
Resets JShell’s state |
|
Resets JShell state and replays history since JShell start or most recent /reset or /reload command |
|
Displays history of all snippets and commands entered since JShell was started |
|
Displays list of JShell commands and help subjects or further information on specified command or subject |
|
Sets JShell configuration options |
|
Retains settings for use in subsequent JShell sessions |
|
Identical to |
|
Re-runs last snippet |
|
Re-runs a snippet referenced by ID |
|