From Python to Java: Using JShell

dylanfw

Dylan

Posted on April 15, 2022

From Python to Java: Using JShell

For most of my career, I've worked primarily as a Python developer. There were occasional trips into the frontend for some JavaScript or a brief detour through a Go application, but the vast majority of my work was solidly Python.

That is, until I joined Square. Square might be better known for its Ruby code, but there is also a sizable chunk of Java within its codebase. So when I came to Square, I knew I'd be putting away my list comprehensions, generators, and @decorators, and instead I'd be learning some new programming language patterns and idioms.

But of course, my Python experience would not be for nothing. As it turns out, we can find parallel ideas throughout most programming languages. In this series, I'll be highlighting some of these parallels I've encountered switching from Python to Java in hopes that it might help someone else who also finds themselves crossing this language barrier!


Introduction

One of the first pain points that a Python developer is going to encounter when picking up Java is the lack of an interpreter. When writing in Python, I would frequently want to "test drive" a short snippet of code. In those moments, I'd pop open a terminal, run python3, and try out my code. This made it easy to make slight adjustments and see the results instantaneously before settling on a solution. If I needed to call an existing function in my codebase, it was a simple import myModule away and suddenly I was interacting with my code in new ways.

Since Java is a compiled language, we can't enjoy this on-the-fly exploratory interaction with our code. We have to wrap our snippets in a big, ugly public static void main(String[] args) method within a class and then wait for it to compile just to see that we actually wanted to write it differently. What if I told you there was a better way?

Enter JShell

No, really. Enter jshell in your terminal. Assuming you have JDK 9+ installed, you should be greeted with the following.1

|  Welcome to JShell -- Version 17.0.2
|  For an introduction type: /help intro

jshell>
Enter fullscreen mode Exit fullscreen mode

JShell was introduced in Java 9 but is still massively underappreciated, so it's understandable if you haven't heard of it until now. JShell is a REPL2 for Java code.

Starting to Explore

Imagine you've just read about the Streams API3 and you want to explore it for yourself before using it in your code. Within JShell, type the following and hit enter.

var names = Stream.of("Alice", "Adam", "Bob", "Charles")
Enter fullscreen mode Exit fullscreen mode

Next, let's practice consuming the stream:

names.filter(name -> name.startsWith("A"))
     .map(String::toUpperCase)
     .collect(Collectors.toList())
Enter fullscreen mode Exit fullscreen mode

Great! You should see the output immediately just like we'd expect when using the Python interpreter.

Play it back

Let's try consuming our stream again within the same session:

names.filter(name -> name.startsWith("A"))
     .map(String::toLowerCase)
     .collect(Collectors.toList())
Enter fullscreen mode Exit fullscreen mode

IllegalStateException: stream has already been operated upon or closed

Looks like streams can only be used once! Rather than re-declaring our initial Stream object, let's take advantage of another JShell feature. Type /list to view a numbered list of every statement we've written in this session. Number 1 should be our names initialization. Let's re-execute that statement! Type /1 to rerun the variable initialization. Once the Stream has been reinstated, we can type /3 to rerun our last snippet!

jshell> /list

   1 : var names = Stream.of("Alice", "Adam", "Bob", "Charles");
   2 : names.filter(name -> name.startsWith("A"))
                   .map(String::toUpperCase)
                   .collect(Collectors.toList())
   3 : names.filter(name -> name.startsWith("A"))
                   .map(String::toLowerCase)
                   .collect(Collectors.toList())

jshell> /1
var names = Stream.of("Alice", "Adam", "Bob", "Charles");
names ==> java.util.stream.ReferencePipeline$Head@2b05039f

jshell> /3
names.filter(name -> name.startsWith("A"))
            .map(String::toLowerCase)
            .collect(Collectors.toList())
$5 ==> [alice, adam]
Enter fullscreen mode Exit fullscreen mode

Interacting with other libraries

Sometimes you need more context. Maybe you want to test out a snippet of code that calls a method of one of your classes or that uses some third-party library. In Python we could simply import myModule to pull our code into the REPL session, or we could start the session already in context with the -m flag, python3 -m myModule.

With JShell the process isn't much different. The only distinction is that our application (or any other dependencies we're trying to work with) must first be added to our class path before we can import them.

Say we'd like to explore the Apache Commons Lang library and we've already downloaded its commons-lang3-3.12.0.jar somewhere on our computer. In order to import its packages within our JShell session, we have two options:

  1. We can start the session with the library already loaded:
    jshell --class-path /path/to/commons-lang3-3.12.0.jar

  2. After starting a new JShell session, we can add it to our environment:
    /env -class-path /path/to/commons-lang3-3.12.0.jar

Once loaded into our JShell environment, we can import its packages and try it out:

jshell> /env -class-path /path/to/commons-lang3-3.12.0.jar

jshell> import org.apache.commons.lang3.StringUtils

jshell> StringUtils.isBlank("  ")
$2 ==> true
Enter fullscreen mode Exit fullscreen mode

Interacting with your own application code follows the same process once you've built your own .jar file.

Tip: Loading in an application's .jar and using its methods within a JShell session can make debugging issues in other environments like staging or production much easier - so long as you exercise the proper caution!

Java script?

One last feature I'd like to call out with JShell is the ability to create Java scripts. No, not Javascript. Java scripts. Essentially, JShell can read its commands from a file rather than from the terminal prompt. For example, save the following as helloWorld.jsh.

System.out.println("Hello world!")
/exit
Enter fullscreen mode Exit fullscreen mode

We can then run it via jshell helloWorld.jsh. You probably shouldn't write all of your scripts in Java now, but it's nice to have the option!

Further Reading

Want to learn more about JShell?


  1. If you get command not found, you might need to specify the full path. Give it a shot with $JAVA_HOME/bin/jshell. Tip: adding $JAVA_HOME/bin to your $PATH might be a good idea. 

  2. REPL stands for Read, Evaluate, Print, Loop. Interpreted languages (Python, Ruby, et al) get REPLs for free - it's just the regular interpreter reading from standard input instead of a .py or .rb file. Compiled languages like Java need a little extra help. Thankfully, we have JShell! 

  3. For a super basic introduction to Streams, check out another entry in this series: From Python to Java: Comprehensions and Streams

💖 💪 🙅 🚩
dylanfw
Dylan

Posted on April 15, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

From Python to Java: Using JShell
programming From Python to Java: Using JShell

April 15, 2022