razzi.abuissa.net

Razzi's guide to Java

2024-03-03

Ok I’m not really a Java expert… it’s pretty verbose and fits mostly the same use cases as Python… but I still come across it frequently.

Ironically, I haven’t written a guide to Python, which I know much better; I guess I know enough that I feel like I wouldn’t do it justice to write a short guide.

Anyways Java and its JVM is the default runtime of Clojure, and the “write once, run anywhere” ideal of Java is pretty cool. And as we’ll see, the tooling is good.

jshell

Most people don’t think “repl” when they think Java. They think “IDE”. But I don’t like IDEs and I like repls. Fortunately Java has a good repl built in. It’s called jshell.

It’s included in Java since version 9 which came out in 2017.

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

jshell>

variables

Declare variables in java with the type like so:

jshell> int x = 3
x ==> 3

jshell> x + 4
$2 ==> 7

Or let java figure out the type using var (since Java 10):

jshell> var y = 5
y ==> 5

However var doesn’t always work.

jshell> var xs = {1, 2, 3}
|  Error:
|  cannot infer type for local variable xs
|    (array initializer needs an explicit target-type)
|  var xs = {1, 2, 3};
|  ^-----------------^

People tend to not use var in production code anyways.

The type syntax for arrays is:

jshell> int[] xs = {1, 2, 3}
xs ==> int[3] { 1, 2, 3 }

The classic “hello world” in the jshell is:

jshell> System.out.println("Hello world")
Hello world

That’s verbose!!

compiling java code

Ok now that we know how to print “Hello world”, let’s make a program that does that.

Java is very particular about filenames… we’ll see why in a second. Let’s add our hello world to a file HelloWorld.java. We need a semicolon now :(

$ echo 'System.out.println("Hello world");' > HelloWorld.java

But when we try to compile that:

$ javac HelloWorld.java
HelloWorld.java:1: error: class, interface, enum, or record expected
System.out.println("Hello world");
^
1 error

Ok so we update HelloWorld.java to the classic Java boilerplate:

public class HelloWorld {
  public static void main() {
    System.out.println("Hello world");
  }
}

Now we can compile it:

$ javac HelloWorld.java
$ ls
HelloWorld.class  HelloWorld.java

As we can see, this produces HelloWorld.class. This is the Java bytecode.

We can run it with java:

$ java HelloWorld
Hello world

compiling and running java in 1 command

If you’re running java 11 or later1, you can compile and run java in 1 command (note the .java extension):

$ java HelloWorld.java
Hello world

more concise main method in java 25

If you’re running java 25 or later2, you can have a main method outside of a class, and it will implicitly create the main class for you. So your whole file can look like:

void main() {
  System.out.println("Hello, java 25!");
}

They also added the IO class as a shortcut:

void main() {
  IO.println("Hello, java 25!");
}

functional java

Java has some functional features that have been added over time.

Honestly one of the big selling points of Java is that the old stuff still works; you can write a for loop that looks like this:

jshell> int[] xs = {1, 2, 3}
xs ==> int[3] { 1, 2, 3 }

jshell> for(int i = 0; i < xs.length; i++) {
   ...>     System.out.println(xs[i]);
   ...> }
1
2
3

Or you can use a foreach style loop:

jshell> for (int x : xs) {
   ...>     System.out.println(x);
   ...> }
1
2
3

But my preferred way to go about this is to use functional programming.

Since it was added much after the fact (and Java is big on object-oriented code) this style has some quirks.

In order to call .forEach which is how to pass a function to work on an element, we can either convert the array to an ArrayList:

jshell> Arrays.asList(xs)
$12 ==> [[I@1b2c6ec2]

jshell> $12.getClass()
$18 ==> class java.util.Arrays$ArrayList

Or just declare a list from the start:

jshell> var l = List.of(4, 5, 6)
l ==> [4, 5, 6]

Now we can iterate over our list:

jshell> l.forEach(x -> System.out.println(x))
4
5
6

That -> is the lambda syntax. Cool!

Since we’re just calling a function with a single argument, you’d think we could do:

jshell> l.forEach(System.out.println)
|  Error:
|  cannot find symbol
|    symbol:   variable println
|  l.forEach(System.out.println)
|            ^----------------^

However it can’t access println as a function; this is where the object-oriented + functional starts to clash somewhat. I guess it could be akin to the old-style Python print statement as opposed to the Python 3 print function. Java wouldn’t do that; it’d break backwards compatibility.

Fortunately they have this syntax for accessing a method as a function:

jshell> l.forEach(System.out::println)
4
5
6

There you have it, functional Java. Had to jump through some hoops but the final result ain’t bad.

assigning a function to a variable

This isn’t necessarily something you’ll want to do in java, but I was curious.

I wanted to assign IO.println to a variable and call it later.

Here’s what I wanted it to look like:

jshell> var method = IO::println
jshell> method("HI")

(which is very similar to this python 3 code)

method = print
method('hi')

However when I tried to assign IO::println to a method, I got this error:

jshell> var method = IO::println
|  Error:
|  cannot infer type for local variable method
|    (method reference needs an explicit target-type)
|  var method = IO::println;
|  ^-----------------------^

Ok, so var isn’t able to figure out what the type of IO::println is.

The easiest way add a type is:

jshell> Consumer<String> method = IO::println
method ==> $Lambda/0x00001e0001049728@6433a2

This also illustrates that the double colon operator :: is syntactic sugar for a lambda:

jshell> Consumer<String> method = s -> IO.println(s)
method ==> $Lambda/0x00001e000104b328@5ce65a89

Now you can can call your method using its accept method:

jshell> method.accept("HI")
HI

The other way I figured out how to do this is to define a @FunctionalInterface. It’s multiple lines but you can add it to jshell just fine.

jshell> @FunctionalInterface
   ...> interface CanPrint {
   ...>   void do_print(String message);
   ...> }
|  created interface CanPrint

jshell> CanPrint method = IO::println
method ==> $Lambda/0x00000ff801048210@17f052a3

jshell> method.do_print("ok")
ok

However I find the Consumer style to be more elegant.

mapping a function over a list

As we’ve seen above, it’s relatively straightforward to have a side effect over a list in Java.

However if you want to apply a function to a list and get a new list, you have to jump through some hoops.

Namely you have to convert to and from a stream. Only stream has the .map function:

jshell> List.of(1, 2, 3).stream().map(x -> x * x).toList()
$1 ==> [1, 4, 9]

It gets yet trickier if you want to apply a named function, rather than a lambda:

jshell> int square(int x) { return x * x; }
|  created method square(int)

jshell> List.of(1,2,3).stream().map(square).toList()
|  Error:
|  cannot find symbol
|    symbol:   variable square
|  List.of(1,2,3).stream().map(square).toList()

The best solution I’ve found is to use another lambda expression.

jshell> List.of(1,2,3).stream().map(x -> square(x)).toList()
$13 ==> [1, 4, 9]

It kinda defeats the purpose of referencing a method directly, but it’s better than the alternative. The only other way I’ve found to do this in the jshell where there is no class context is to declare a utility class, in this case Utils:

jshell> class Utils { static int square(int x) { return x * x; }}
|  created class Utils

jshell> List.of(1).stream().map(Utils::square).toList()
$12 ==> [1]

Putting it all together, here’s how you can totally unnecessarily write and call a named lambda:

jshell> Function<Integer, Integer> square = x -> x * x
square ==> $Lambda/0x00003f800104aa10@421faab1

jshell> List.of(1, 2, 3).stream().map(x -> square(x)).toList()
$17 ==> [1, 4, 9]
  1. See https://openjdk.org/jeps/330

  2. See https://openjdk.org/jeps/512