“Simplicity is the ultimate sophistication.” Leonardo da Vinci
Intro
Everybody is freaking out about the new Java 9 release which came out in September 2017, because Java Standard Edition 9 is a major release which brought a lot of new features, some will say around 55. I’m not going to list all the features or discuss in detail all of them, but I’ll try to unveil one of the most exciting features – JShell.
What is JShell?
According to Oracle, “Java Shell tool (JShell) is an interactive tool for learning the Java programming language and prototyping Java code. JShell is a Read-Evaluate-Print-Loop (REPL), which evaluates declarations, statements, and expressions as they are entered and immediately shows the results. The tool is run from the command line.”
JShell acts like a UNIX shell: it reads the instructions, evaluates them, prints the result of the instructions, and then displays a prompt while waiting for new commands. It is built around several core concepts – snippet, state, instruction modification and snippet dependencies (I’ll try to explain it later).
Java 9 introduced an interactive REPL command-line environment named JShell. This tool allows us to execute Java code snippets and get immediate results. We can easily write code and see the results of its execution without having to create a solution or project. We don’t have to wait for the project to finish the build process in order to check the results of the execution of some lines of code.
JShell, as any other REPL, facilitates exploratory programming, that is, we can easily and interactively try and debug different new features, algorithms or even interesting puzzles.
It’s worth noting that there are already a few options for REPL – like Java BeanShell functionality from the Java ecosystem, which can be a good option, but is a third party-tool that’s not standard, nor it is available by default, as well as misses some features such as being able to save the script out to a file for later use.
Installing and launching
Installing JShell is not a big thing since it comes with JDK 9, which you can download right now, as it was already released.
Once you downloaded it, make sure that you update your JAVA_HOME variable so that it will point to Java 9. Run java –version
to verify your installation. The output should look like this one.
[code language=”shell”]
C:Windowssystem32>java -version
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
[/code]
Now to run JShell, type jshell
in the command line. You should get something like this:
[code language=”shell”]
C:Windowssystem32>jshell
| Welcome to JShell — Version 9
| For an introduction type: /help intro
jshell>
[/code]
Snippets
To understand how JShell works, let’s take a look at a few snippets. But wait, what is a snippet? A snippet is an instruction that uses standard Java syntax. It represents a single expression, statement, or declaration. The following is a simple snippet. The text below the command is the JShell output:
[code language=”java”]
jshell> System.out.println("Hi Inther")
Hi Inther
[/code]
JShell allows you to declare variables, methods and even classes, isn’t that amazing? But before jumping into JShell, let’s have a look at a few rules :
- A snippet should correspond to one of the following from JLS:
- Import Declarations
- Class Declarations
- Interface Declarations
- Method Declarations
- Field Declarations
- Statements
- Primary Expressions
- Package declarations are not allowed, JShell code is placed under transient JShell package.
- Access modifiers (
public
,protected
, andprivate
) and the modifiersfinal
andstatic
are not allowed in the top-level declarations, if provided they are ignored by warning. - The modifiers
default
andsynchronized
are not allowed at all in the top-level declarations, however they are allowed in a nested context. abstract
modifier is allowed only on classes.- When the user input is incomplete (e.g. you type only
System.out
and skip theprintln
part) JShell autocompletion API prompts for a more user input. - If the input is complete but there is no semicolon JShell will append it automatically.
Here are some trivial examples:
[code language=”shell”]
jshell> int x, y, sum
x ==> 0
y ==> 0
sum ==> 0
jshell> x = 10; y = 20; sum = x + y
x ==> 10
y ==> 20
sum ==> 30
jshell> System.out.println("Sum of " + x + " and " + y + " = " + sum)
Sum of 10 and 20 = 30
[/code]
Also, we can declare a class like this:
[code language=”shell”]
class Employee{
private String firstName;
private String lastName;
private String position;
private String workplace;
public Employee(String firstName, String lastName, String position, String workplace){
this.firstName = firstName;
this.lastName = lastName;
this.position = position;
this.workplace = workplace;
}
public String getFirstName(){
return firstName;
}
public String getLastName(){
return lastName;
}
public String getJobPosition(){
return position;
}
public String getWorkplace(){
return workplace;
}
public String toString(){
return "Name = " + firstName + ", " + lastName + " | " +
"Job Position = " + position + " | " +
"Workplace = " + workplace + ".";
}
}
[/code]
And then we can simply instantiate it like we always do in Java:
[code language=”shell”]
jshell> Employee e = new Employee("Ion", "Pascari", "Programmer", "ISD")
e ==> Name = Ion, Pascari | Job Position = Programmer | Workplace = ISD.
[/code]
The indentation looks different than in Java, because this code was typed in the JShell command line. Some normal Java statements are not needed at this initial declaration. For example, JShell automatically imports many typical packages. In our example, the following imports were done automatically:
[code language=”shell”]
jshell> /import
| 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.*
[/code]
Now, we’re going to discuss some commands.
Commands
JShell commands control the environment and display information during a session. Commands are distinguished from snippets by a leading forward slash (/). To check all the available command, type /help
. For more information about the current variables, methods, and types, use the /vars
, /methods
, and /types
commands. For a list of entered snippets, use the /list
command.
[code language=”shell”]
jshell> /vars
| int x = 10
| int y = 20
| int sum = 30
| Employee e = Name = Ion, Pascari | Job Position = Programmer | Workplace = ISD.
jshell> /methods
jshell> /types
| class Employee
jshell> /list
1 : System.out.println("Hi Inther")
2 : int x, y, sum;
3 : x = 10;
4 : y = 20;
5 : sum = x + y
6 : System.out.println("Sum of " + x + " and " + y + " = " + sum)
7 : class Employee{
private String firstName;
private String lastName;
private String position;
private String workplace;
public Employee(String firstName, String lastName, String position, String workplace){
this.firstName = firstName;
this.lastName = lastName;
this.position = position;
this.workplace = workplace;
}
public String getFirstName(){
return firstName;
}
public String getLastName(){
return lastName;
}
public String getJobPosition(){
return position;
}
public String getWorkplace(){
return workplace;
}
public String toString(){
return "Name = " + firstName + ", " + lastName + " | " +
"Job Position = " + position + " | " +
"Workplace = " + workplace + ".";
}
}
8 : Employee e = new Employee("Ion", "Pascari", "Programmer", "ISD");
[/code]
Similar to snippet completion, when you input the commands and their options, use the Tab key to automatically complete the command or the option. If the completion can’t be determined from what was entered, then possible choices are provided.
Reduce the amount of typing you have to do by using abbreviations. Commands, /set
subcommands, command arguments, and command options can all be abbreviated, as long as the abbreviation is unique. The only command that begins with /l
is /list
, and the only /list
option that begins with -a is –all
. So you can do something like this:
[code language=”shell”]
jshell> /l –a
[/code]
Another nice thing provided by JShell are the hotkeys, for example, we can do some searching through the history, which makes it easier to find the line you want without going through the history one line at a time. To start your search, press Ctrl-R. At the prompt, enter the search string. The search proceeds backward from your most-recent entry and includes previous sessions of JShell or you can press Ctrl-S for a forward search:
[code language=”shell”]
jshell>
(reverse-i-search)`E’: Employee e = new Employee("Ion", "Pascari", "Programmer", "ISD")
[/code]
Editing
An alternative way of editing in the command prompt is to use an external editor – JShell Edit Pad. This editor can be used to edit and create snippets, and is especially helpful for multi line snippets, when declaring classes it becomes very handy.
To edit all the existing snippets at once in an editor, use /edit
without an option. To edit a specific snippet in an editor, use the /edit
command with the snippet name or its ID. Use the /list
command to get the snippet IDs. The following example opens an editor to edit the snippet with ID = 2 where we declared the variables x, y, and sum. By clicking Accept, we’re confirming the changes. Check the Fig. 1 to see how the editor looks like:
[code language=”shell”]
jshell> /edit 2
x ==> 0
y ==> 0
sum ==> 0
dif ==> 0
[/code]
The good news is that JShell allows us to easily configure any external editor for editing the code snippets. We just need to grab the absolute path to the editor we want to use and run a command in JShell, to configure the editor we want to launch whenever we use the /edit
command. You can configure JShell to use the editor of your choice with /set
editor and then append the path. When setting the path we have to make sure that we replace the backslash () with double backslashes (\) in the path string. For example, I’ll set Notepad++ with the following command:
[code language=”shell”]
jshell> /set editor "C:\Program Files (x86)\Notepad++\notepad++.exe"
| Editor set to: C:Program Files (x86)Notepad++notepad++.exe
[/code]
Voilà, now running the same edit on the same snippet will look like in Fig. 2.
Also using the /open
command with the appended path of a Java file we can easily edit or test out some code created earlier.
A little about states
Okay, another specific thing for the REPL is that it has states. Each statement in JShell has a state. The state denies the execution status of snippets and of variables. It is determined by the results of the eval()
method of the JShell instance, which evaluates the code. There are 7 status states:
- DROPPED: The snippet is inactive.
- NONEXISTENT: The snippet is inactive because it does not yet exist.
- OVERWRITTEN: The snippet is inactive because it has been replaced by a new snippet.
- RECOVERABLE_DEFINED: The snippet is a declaration snippet with potentially recoverable unresolved references or other issues in its body.
- RECOVERABLE_NOT_DEFINED: The snippet is a declaration snippet with potentially recoverable unresolved references or other issues.
- REJECTED: The snippet is inactive because the compilation failed upon initial evaluation and it is not capable of becoming valid with further changes to the JShell state.
- VALID: The snippet is a valid snippet.
JShell from a program?
The JDK offers APIs for us to access JShell programmatically rather than by using the REPL. The code below creates an instance of JShell, evaluates a snippet, and checks on the status of the instruction.
[code language=”shell”]
import java.util.List;
import jdk.jshell.*;
import jdk.jshell.Snippet.Status;
class JShellDemo
{
public static void main(String… args) {
JShell shell = JShell.create();
List<SnippetEvent> events = shell.eval( "int a, b, sum; " +
"a = 12; b = 11; sum = a + b; " +
"System.out.println(sum);" );
for(SnippetEvent event : events) {
Snippet snippet = event.snippet();
Snippet.Status snippetstatus = shell.status(snippet);
if(snippetstatus == Status.VALID) {
System.out.println("Successful executed.");
}
}
}
}
Output>
Successful executed.
Successful executed.
Successful executed.
[/code]
Here you will find a more detailed information on this API.
Handling external code
But what if we want to test an external library, to see how it works? Well, external classes are accessed from a JShell session through the class path. So let’s say that we want to play a little bit with the Gson library which is a Java serialization/deserialization library intended for converting Java Objects into JSON and back. So in order to do that, we need to set the classpath on the command line as shown below:
[code language=”shell”]
jshell> /env –class-path D:IntherWorkspacegson-2.8.2.jar
| Setting new options and restoring state.
[/code]
After that we can import our library, check on it and simply do the following:
[code language=”shell”]
jshell> import com.google.gson.*
jshell> /import
| 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.*
| import java.util.List
| import jdk.jshell.*
| import jdk.jshell.Snippet.Status
| import com.google.gson.*
jshell> Gson g = new GsonBuilder().setPrettyPrinting().create()
g ==> {serializeNulls:false,factories:[Factory[typeHier … 17f9],instanceCreators:{}}
jshell> String empSerialized = g.toJson(e)
empSerialized ==> "{n "firstName": "Ion",n "lastName": " … "workplace": "ISD"n}"
jshell> System.out.println(empSerialized)
{
"firstName": "Ion",
"lastName": "Pascari",
"position": "Programmer",
"workplace": "ISD"
}
jshell> Employee e1 = g.fromJson(empSerialized, Employee.class)
e1 ==> Name = Ion, Pascari | Job Position = Programmer | Workplace = ISD.
[/code]
As simple as it gets, we serialized an employee into a string and then deserialized it into an another employee.
Feedback modes
I’m not going to dive into too much details, just want to make sure that you know that there is a possibility to change the prompts and feedback that are used in your interaction with JShell. The following predefined modes are provided for your convenience :
The default feedback mode is normal. To change it you can use the /set
feedback command and just append the desired feedback mode.
[code language=”shell”]
jshell> /set feedback silent
-> /help
…
[/code]
Conclusion
To conclude I would say that JShell is a very useful tool for prototyping and testing Java code snippets. JShell provides us with the appropriate tool to start interacting with a library in a few seconds. We just need to launch JShell, load the library, and start writing Java 9 code in the REPL. With previous Java versions, we would have needed to create a new project from scratch and write some boilerplate code before we could start writing the first lines of code that we really needed to test.
JShell allows us to start working faster and reduces the need to create an entire skeleton to start running Java code. We can enter any Java definition in JShell. For example, we can declare methods, classes, and variables. We can also enter Java expressions, statements, or imports, almost anything.
Hope that you enjoyed reading this and learned something new. There’s more to come so stay tuned.