using Java 9’s jshell for experimenting

In Java 8, Oracle introduced the new Nashorn engine and is encouraging the use of jjs. It’s a RPEL (read print evaluate loop) for JavaScript. Since JavaScript can call Java, this allows experimenting with Java APIs like you do in other languages like Python and Ruby. I actively disliked it and even wrote a blog post about what turned me off. Java 9 introduced JShell for this same purpose except it works with Java rather than a level of indirection through JavaScript.

In this blog post, I repeat the same experiment with JShell (using the Java 9 early access download) to see if it is any better. Again I used the exercises from Cay Horstmann’s book, Java SE 8 for the Really Impatient so it would be an equivalent experiment.

Exercise 1 – Play … to experiment with an API you want to explore; did you find it easier than writing test programs in Java?

With Nashorn/jjs in Java 8, I had three issues. Let’s see how jjs compares:

Nashorn/jjs JShell
Strike 1 – needing to type in the package name of classes. Better. At least common packages are included now like java.util. For others, like LocalDate, you still need to type the package name. Which often means looking up the package name. I rely heavily on “organize imports” in my IDE for package names.
Strike 2 – no tab complete Success! JShell has tab complete for packages, classes and methods
Strike 3 – no up arrow support Success! JShell lets you you use the up/down arrows to go through your command history.

Having solved these three issues, the question is a better one. If I’m on a UNIX system or don’t have my IDE open, JShell would definitely be faster. If I already have the IDE open, I have a “PlayTest” program with a main method already available to me so it is a tie. But that’s just my workflow. JShell looks good.

Note: I learned on Twitter that:

wickund: @jeanneboyarsky jjs in JDK9 uses the same API as jshell (JDK9), so there is code completion and up arrow.

Which means my issues with Nashorn are gone in Java 9. Thanks Jim Laskey

Exercise 2 – Using …. the stream library iteratively work out a solution to the problem to print a filtered list of unique long words in sorted order and see how it compares to your usual workflow.

Here was my workflow with JShell:

  1. Create the test file in a text editor (vi)
  2. Run ./jshell
  3. Create a path object
    Attempt # Code Error Comments
    1 jshell> Path paths = Path.get(“path/words.txt”) Success! This package is included in the list of known ones so I didn’t even have to look anything up.
  4. Read the file
    Attempt # Code Error Comments
    1 jshell> List<String> list = Files.readAllLines(path) Success
  5. Filter the long words
    Worked.

    Attempt # Code Error Comments
    1 jshell> list.stream().grep({ l -> l.length() > 12}) cannot find symbol This is completely wrong. Aside from the stream() call, it is using Groovy syntax. (I code Groovy at a command line so my fingers started there. I did the same thing when I was doing this with Nashorn)
    2 jshell> list.stream().filter( l -> l.length() > 12) Success
  6. Add sorting and uniqueness
    Attempt # Code Error Comments
    1 jshell> list.stream().filter(l -> l.length() > 12).sorted().forEach(System.out::println) Sorting worked
    2 jshell> list.stream().filter(l -> l.length() > 12).sorted().distinct().forEach(System.out::println) Uniqueness filter worked

 My thoughts

This was 6 attempts/iterations using JShell.  This is compared to 17 in Nashorn! JShell is clearly a superior tool for prototyping Java. Part of this is because they implement common RPEL features. And part is because they don’t try to shoehorn a rhinoceros into being a Java RPEL. Seriously, many of the problems with Nashorn came from mixing Java and JavaScript while trying to prototype Java.

using Nashorn’s jjs for experimenting

In Java 8, Oracle introduced the new Nashorn engine and is encouraging the use of jjs. It’s a RPEL (read print evaluate loop) for JavaScript. Since JavaScript can call Java, this allows experimenting with Java APIs like you do in other languages like Python and Ruby. It works better in those languages though.

Cay Horstmann’s book, Java SE 8 for the Really Impatient, had good exercises for lambdas so I decided to the end of chapter exercises for Nashorn.

Exercise 1 – Play with jjs to experiment with an API you want to explore; did you find it easier than writing test programs in Java?

Nope. The so called advantage of using jjs is that you don’t need to write the plumbing code. But an IDE can generate a class and main method in a couple clicks.

  1. Strike 1 – With jjs, I need to type in the package of name of classes I want to use rather than just letting Eclipse deal with imports for me.
  2. Strike 2 – jjs doesn’t have tab complete (autocomplete) which means I have to type in the full class/method name of the class I am experimenting with. If I was an expert, I wouldn’t be in jjs in the first place.
  3. Strike 3 – jjs doesn’t support the up arrow. Which means when I make typos (see strike 1 and 2), I get to type the whole thing again.

Exercise 2 – Using JJS and the stream library iteratively work out a solution to the problem to print a filtered list of unique long words in sorted order and see how it compares to your usual workflow.

Here was my workflow with jjs:

  1. Create the test file in a text editor
  2. Run ./jjs
  3. Create a path object
    Attempt # Code Error Comments
    1 jjs> var paths = Paths.get(‘path/words.txt’) <shell>:1 ReferenceError: “Paths” is not defined Forgot to type the package name. Don’t know it by heart anyway. Need to look it up in the JavaDoc.
    2 jjs> var paths = java.nio.file.Paths.get(‘path/words.txt’) Success
  4. Read the file
    Attempt # Code Error Comments
    1 jjs> List<String> list = java.nio.files. Files.readAllLines(path) ECMAScript Exception: ReferenceError: <shell>:1:11 Invalid left hand side for assignment It’s JavaScript, not Java so no type declaration. Should just use var.
    2 jjs> var list = java.nio.files. Files.readAllLines(path)  <shell>:1 ReferenceError: “path” is not defined Typo in variable name. I wish I named it path, not paths. But I want to do the exercise in order and not go back.
    3 jjs> var list = java.nio.files. Files.readAllLines(paths) java.lang.RuntimeException: java.lang. ClassNotFoundException: java.nio.files. Files.readAllLines Typo in package name. It should be file and not files
    4 jjs> var list = java.nio.file. Files.readAllLines(paths) java.lang.RuntimeException: java.nio.file. NoSuchFileException: path/words.txt Missing forward slash in path name. On the bright side, I can rename the variable without feeling like I’m messing up the experiment.
    5 jjs> var path = java.nio.file. Paths.get(‘/path/words.txt’) Worked as expected (but at this point, I’m typing into this blog post and copy/pasting into the jjs console
    6 jjs> var list = java.nio.file. Files.readAllLines(paths)  java.lang.RuntimeException: java.nio.file. NoSuchFileException: path/words.txt Sigh. Didn’t use the corrected variable name
    7 var list = java.nio.file. Files.readAllLines(path) Worked as expected
  5. Filter the long words
    Worked.

    Attempt # Code Error Comments
    1 jjs> list.stream().grep({ l -> l.length() > 12}) ECMAScript Exception: SyntaxError: <shell>:1:23 Expected : but found – This is completely wrong. Aside from the stream() call, it is using Groovy syntax. (I code Groovy at a command line so my fingers started there)
    2 jjs> list.stream().filter( l -> l.length() > 12) ECMAScript Exception: SyntaxError: <shell>:1:30 Expected an operand but found Hmm. Puzzled. it looks right this time. I copy/pasted into my IDE and it compiled. I then started searching online and found out that you have to use a function literal. (This was in the book. I forgot about it)
    3 jjs> list.stream().filter(function(e) { return e.length() > 12 }) Prints reference to stream (as does Java.) Now can add the print
    4 jjs> list.stream().filter(function(e) { return e.length() > 12 }).forEach(System.out::println) ECMAScript Exception: SyntaxError: <shell>:1:79 Expected , but found : Not surprised that :: doesn’t work since -> doesn’t. Now for the long way.
    5 jjs> list.stream().filter(function(e) { return e.length() > 12 }).forEach(function(e) { print(e) }) Works as expected
  6. Add sorting, uniqueness and refactor
    Attempt # Code Error Comments
    1 jjs> list.stream().filter(function(e) { return e.length() > 12 }).sorted().forEach(function(e) { print(e) }) Sorting worked
    2 jjs> list.stream().filter(function(e) { return e.length() > 12 }).sorted().distinct().forEach(function(e) { print(e) }) Uniqueness filter worked
    3 jjs> list.stream().filter(function(e) e.length() > 12 ).sorted().distinct().forEach(function(e)  print(e) ) Remove unneeded code

 My thoughts

That was so far away from being easier that it was ridiculous.  I’m comfortable with the NIO and lambda APIs. This shouldn’t have been hard. The Java version is:

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class Exercise2 {
   public static void main(String[] args) throws IOException {
      Path path = Paths.get("/path/words.txt");
      List<String> list = java.nio.file.Files.readAllLines(path);
      list.stream()
                .filter(l -> l.length() > 12)
                .sorted()
                 .distinct()
		.forEach(System.out::println);
     }
}