Java 9 Gets an Interactive Shell

With the release of Java 9, an interactive shell (otherwise known as a REPL – read, eval, print loop) is now available. Many languages like Python, Ruby, PHP, and MySQL have had interactive shells since their inception. Javascript got a REPL with the introduction of node (although the developer consoles in browsers have offered a similar functionality before node). But for a long time Java stood with C, C++, and Objective C as computer languages that have not offered an interactive shell.

The general idea of a REPL is almost as old as computing technology itself. It originated in early versions of the Lisp programming language that was first developed in the late 1950s; “read”, “eval”, and “print” were all executable functions even in early versions of Lisp.

In Java 9, this interactive shell is started with the “jshell” command:

$ jshell
|  Welcome to JShell -- Version 9.0.4
|  For an introduction type: /help intro
 
jshell>

Say you want to find the current date and time…easily done:

jshell> new Date()
$1 ==> Fri Jan 19 13:34:19 EST 2018

…or maybe the number of milliseconds since the Unix epoch (or convert back to the date from Unix epoch milliseconds):

jshell> new Date().getTime()
$2 ==> 1516387214985
 
jshell> System.currentTimeMillis()
$3 ==> 1516387225625
 
jshell> new Date(1516387225625L)
$3 ==> Fri Jan 19 13:40:25 EST 2018

(note that an “L” had to be added to the end of the millisecond value to explicitly declare it as a long integer value when converting back to Date, otherwise an “integer number too large” error occurs)

This Unix epoch scenario can be very useful when working with applications that maintain timestamps on data or log entries (up until now I’ve kept a small Java program around to do these kinds of conversions between current time and Unix milliseconds).

Note that I didn’t have to do an import of the “java.util” package here to get access to the Date class – I simply referenced the constructor directly. As a convenience, “jshell” automatically imports some of the most commonly used core java packages. The “/import” command shows what’s been imported:

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

To exit jshell, you can press “Ctrl-D” or enter the “/exit” command.

As another example, system properties can be examined in the interactive shell:

jshell> System.getProperty("java.class.path")
$4 ==> ":/usr/local/junit/junit.jar:/usr/local/aspectj/lib/aspectjrt.jar"
 
jshell> System.getProperties()
$5 ==> {gopherProxySet=false, awt.toolkit=sun.lwawt.macosx.LWCToolkit,
java.specification.version=9,...more text...

The “Properties” object extends Hashtable, so all properties can be listed as (key, value) pairs like this:

System.getProperties().forEach((k, v) -> { System.out.printf("%s: %s\n", k, v); })
gopherProxySet: false
awt.toolkit: sun.lwawt.macosx.LWCToolkit
java.specification.version: 9
file.encoding.pkg: sun.io
sun.cpu.isalist: 
sun.jnu.encoding: UTF-8
...more lines...

Some languages, for example Python have a “range” function that is useful in various contexts in an interactive shell. Java 8 added support for a stream that returns a range of integers:

jshell> IntStream.range(1,5).forEach(System.out::println)
1
2
3
4
 
jshell> IntStream.rangeClosed(1,5).forEach(System.out::println)
1
2
3
4
5

Note that the default IntStream.range() is open ended on the end – the sequence does not return the integer specified as the ending arg. Another version of the function, IntStream.rangeClosed(), does return the last value.

Want to find the full package name of a class?

jshell> Properties.class
$1 ==> class java.util.Properties
 
jshell> Stream.class
$2 ==> interface java.util.stream.Stream
 
jshell> IntStream.class
$3 ==> interface java.util.stream.IntStream

Here’s a more extended example, reading lines from a file:

jshell> FileReader fr=new FileReader("world-capitals.txt")
fr ==> java.io.FileReader@6325a3ee
 
jshell> BufferedReader br=new BufferedReader(fr)
br ==> java.io.BufferedReader@d2b9627bc
 
jshell> br.readLine()
$3 ==> "Country,Capital"
 
jshell> br.readLine()
$4 ==> "Afghanistan,Kabul"
 
jshell> br.readLine()
$5 ==> "Albania,Tirana"

As demonstrated above, variables can be defined to hold the results of statements which can then be used in other statements. If a variable isn’t explicitly defined, the value of a statement is saved under a numbered “scratch” variable with a name of the form “$n”. The “/vars” command will list all variables that have been created in the shell session:

jshell> /vars
|    FileReader fr = java.io.FileReader@6325a3ee
|    BufferedReader br = java.io.BufferedReader@2b9627bc
|    String $3 = "Country,Capital"
|    String $4 = "Afghanistan,Kabul"
|    String $5 = "Albania,Tirana"

This code to read from a file can be written more concisely without intermediate variables:

jshell> new BufferedReader(new FileReader("world-capitals.txt")).readLine()
$1 ==> "Country,Capital"

Or, read a file using the newer Files.lines method (introduced in Java 8) and Paths class (available since Java 7):

jshell> Files.lines(Paths.get("world-capitals.txt")).limit(6).forEach(wc -> {System.out.println(wc);})
Country,Capital
Afghanistan,Kabul
Albania,Tirana
Algeria,Algiers
Andorra,Andorra la Vella
Angola,Luanda
 
jshell> Files.class
$1 ==> class java.nio.file.Files
 
jshell> Paths.class
$2 ==> class java.nio.file.Paths

Exploring the Java API with Tab Completion

JShell offers ways to explore details of any Java class and the methods that are in it, all through the magic of tab completion.

Enter any variable or constant value (like a string or an int) followed by a period (“.”) then press the “tab” key; the instance methods available on the class of that variable will be displayed. For example, on a string value:

jshell> "mystring".<TAB>
charAt(                chars()                codePointAt(           codePointBefore(       
codePointCount(        codePoints()           compareTo(             compareToIgnoreCase(   
concat(                contains(              contentEquals(         endsWith(              
equals(                equalsIgnoreCase(      getBytes(              getChars(              
getClass()             hashCode()             indexOf(               intern()               
isEmpty()              lastIndexOf(           length()               matches(               
notify()               notifyAll()            offsetByCodePoints(    regionMatches(         
replace(               replaceAll(            replaceFirst(          split(                 
startsWith(            subSequence(           substring(             toCharArray()          
toLowerCase(           toString()             toUpperCase(           trim()                 
wait(

Enter a class name (like “String”) followed by a period and a tab, and the static class methods are listed:

jshell> String.<TAB>
CASE_INSENSITIVE_ORDER   class                    copyValueOf(             
format(                  join(                    valueOf(

Start entering a method call on a variable up to the opening parentheses, then press tab to see possible argument overloads of the method:

jshell> String s="hello"
s ==> "hello"
 
jshell> s.indexOf(
Signatures:
int String.indexOf(int ch)
int String.indexOf(int ch, int fromIndex)
int String.indexOf(String str)
int String.indexOf(String str, int fromIndex)
 
<press tab again to see documentation>

Press tab a second time and documentation for the methods is displayed:

shell> "mystring".indexOf(<TAB><TAB>
int String.indexOf(int ch)
Returns the index within this string of the first occurrence of the
specified character.If a character with value ch occurs in the
character sequence represented by this String object,
...more text...

I’ve found this to be really useful support for finding features of Java as I’m exploring in JShell, similar to what’s provided in other interactive shells (such as the “dir()” and “help()” functions in Python).

Examining History and Saving Code

The “/history” command will list whatever commands have been entered:

jshell> /history
 
FileReader fr=new FileReader("world-capitals.txt")
BufferedReader br=new BufferedReader(fr)
br.readLine()
Files.lines(Paths.get("world-capitals.txt")).limit(6).forEach(wc -> {System.out.println(wc);})
/history

The “/!” command will reexecute the last command entered (skipping administrative commands like “/history” if that was last):

jshell> /!
Files.lines(Paths.get("world-capitals.txt")).limit(6).forEach(wc -> {System.out.println(wc);})
Country,Capital
Afghanistan,Kabul
Albania,Tirana
Algeria,Algiers
...more text...

The up and down arrows can also be used to navigate back and forth in history; when you find an interesting command, press carriage return to execute it again.

Say you’ve been hacking away at Java for a while, and you’ve developed some good code that you want to save off and perhaps use later. The “/save” command will save all code currently in the JShell buffer to a file that you name:

jshell> /save mybestcode.jsh

JShell will give you no feedback that the file is saved, but open a separate terminal window and you’ll see that the file has been written and that it contains the code that is in JShell:

$ cat mybestcode.jsh
FileReader fr=new FileReader("world-capitals.txt");
BufferedReader br=new BufferedReader(fr);
br.readLine()
Files.lines(Paths.get("world-capitals.txt")).limit(6).forEach(wc -> {System.out.println(wc);})

The complementary “/open” command restores code that was saved to a file. In this next example, a new shell session is started; the “/history” command shows no history and the “/list” command shows no code yet in the shell.

$ jshell
|  Welcome to JShell -- Version 9.0.4
|  For an introduction type: /help intro
 
jshell> /history
 
/history
 
jshell> /list
 
jshell> /open mybestcode.jsh
Country,Capital
Afghanistan,Kabul
Albania,Tirana
Algeria,Algiers
Andorra,Andorra la Vella
Angola,Luanda
 
jshell> /list
 
   1 : FileReader fr=new FileReader("world-capitals.txt");
   2 : BufferedReader br=new BufferedReader(fr);
   3 : br.readLine()
   4 : Files.lines(Paths.get("world-capitals.txt")).limit(6).forEach(wc -> {System.out.println(wc);})

The command “/open mybestcode.jsh” loads the code previously saved to file and executes it, resulting in some output. Afterward, the “/list” command shows that there is code again in JShell.

And Yet More Features

JShell offers a bunch of other features for interactively exploring code, such as the ability to open a separate editor window with “/edit” to collectively edit all statements entered so far in the session.

You can also define methods and classes with a little careful editing in the Shell command line (this is where the “/edit” window might be a more practical tool).

There are also ways to specify Java class paths and Java 9 modules that are to be made available in a JShell session.

Enter “/help” at the JShell prompt or “jshell –help” on the system command line for a full list of available commands and options.

Things to Watch Out For

JShell does not require a semicolon at the end of statements entered in the command line, but it does require them on statements entered in code blocks within the statements.

So this line is OK without a terminating semicolon at the very end:

jshell> System.getProperties().forEach((k, v) -> { System.out.printf("%s: %s\n", k, v); })
gopherProxySet: false
awt.toolkit: sun.lwawt.macosx.LWCToolkit
java.specification.version: 9
file.encoding.pkg: sun.io
sun.cpu.isalist: 
...more text...

…but the call to “System.out.printf” in the block of the lambda function must be terminated with a semicolon, otherwise an error occurs:

shell> System.getProperties().forEach((k, v) -> { System.out.printf("%s: %s\n", k, v) })|  Error:
|  ';' expected
|  System.getProperties().forEach((k, v) -> { System.out.printf("%s: %s\n", k, v) })|                                                                                ^

Similarly, any Java code can be executed directly in JShell without having to use a try…catch statement to catch exceptions. But any code you enter within a function that’s declared in JShell has to either catch such exceptions or add “throws ” to the method’s declaration. For example, let’s say you wanted to encapsulate the above “Files…Paths…” code in a reusable function for reading the contents of the file. Here’s how that’s done:

void showfile(String fpath) throws IOException {
    Files.lines(Paths.get(fpath)).limit(6).forEach(wc -> {System.out.println(wc);});
}

JShell would not accept this code without declaring “throws IOException” on the function:

jshell> void showfile(String fpath) {   ...> Files.lines(Paths.get(fpath)).limit(6).forEach(wc -> {System.out.println(wc);});
   ...> }
|  Error:|  unreported exception java.io.IOException; must be caught or declared to be thrown|  Files.lines(Paths.get(fpath)).limit(6).forEach(wc -> {System.out.println(wc);});|  ^---------------------------^

Can’t Find “jshell” After Installing Java 9

I installed Java 9 on my Mac OS X system using the .dmg file downloaded from the Oracle Java SDK site. Afterwards I found that, while running the standard Java under got me into Java 9, “jshell” could not be found on the command line path. It seems that Java gets installed in two locations in Mac OS X, under /System/Library/Frameworks/JavaVM.framework and also /Library/Java/JavaVirtualMachines. The ‘java’ executable that is on Mac OS X PATH by default is under /usr/bin/java is a symbolic link to the install under /System:

$ java -version
java version "9.0.4"
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)
 
$ which java
/usr/bin/java
 
$ ls -l /usr/bin/java
lrwxr-xr-x  1 root  wheel  74 Jun 25  2016 /usr/bin/java@ -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java

It looks like the install of java under /System/Library is the JRE, while the install under /Library is the JDK (with all previous JDK installs preserved under the latter location):

$ ls /Library/Java/JavaVirtualMachines
jdk-9.0.4.jdk/		jdk1.7.0_45.jdk/	jdk1.8.0_40.jdk/

Adding the following to my “.profile” file allowed “jshell” to be found under the other, “/Library” location:

JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home
export JAVA_HOME
PATH=${PATH}:${JAVA_HOME}/bin

TL; DR

The Java language joins a number of other languages in supporting an interactive shell (or REPL, “read-eval-print loop”) for interactive experimentation with features of the language.

Some things to try in JShell to get a feel for its features:

new Date()
new Date().getTime()
System.currentTimeMillis()
new Date(1516387225625L)
System.getProperty("java.class.path")
System.getProperties()
System.getProperties().forEach((k, v) -> { System.out.printf("%s: %s\n", k, v); })
IntStream.range(1,5).forEach(System.out::println)
IntStream.class
new BufferedReader(new FileReader("some-text-file.txt")).readLine()
Files.lines(Paths.get("some-text-file.txt")).limit(6).forEach(wc -> {System.out.println(wc);})
"mystring".indexOf(<TAB>
/imports
/vars
/history
/save "saved-file.jsh"
/open "saved-file.jsh"
/edit "saved-file.jsh"

Things to watch out for:

  • Java statements within function or lambda function definitions entered at the “jshell>” prompt have to be terminated with semicolons as they would in regular Java source code (simple statements entered directly at the “jshell>” prompt don’t need the semicolon)
  • functions or methods defined in JShell which contain code that could throw a Java checked exception must either catch the exception or declare it as thrown from the method (exceptions are implicitly caught and handled for code entered directly at the shell prompt)

References

A nice tutorial introduction to JShell:
Introduction to JShell

The original announcement of JShell:
JShell and REPL in Java 9 | The Java Source

Versions

$ java –version
java 9.0.4
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)

jshell> System.getProperty(“java.version”)
$1 ==> “9.0.4”

Add a Comment

Your email address will not be published. Required fields are marked *