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.
- 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.
- 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.
- 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:
- Create the test file in a text editor
- Run ./jjs
- 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 - 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 - 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 - 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); } }
If using Java 9, use jshell! See the comparison at https://www.selikoff.net/2017/05/13/using-java-9s-jshell-for-experimenting/
Note: Java 9 solves some of these shortcomings