Wednesday, March 22, 2023

Quiz yourself: The strange case of the Java developer’s birthday

Quiz Yourself, Java developer’s Birthday, Oralce Java Certification, Java Career, Java Prep, Java Guides, Java Tutorial and Materials, Java Learning, Java Guides Exam

You want to know what the day of the week will be, one year from today.


On her birthday one August, a Java developer decided to check what day of the week her next birthday will fall on.

Given the following partial code

LocalDate birthday = LocalDate.now();
// line n1
DayOfWeek dow = birthday.getDayOfWeek();

Which two of the following could be added at line n1 to perform this calculation? Choose two.

A. birthday = birthday.plus(365, ChronoUnit.DAYS);
B. birthday.plus(365, ChronoUnit.DAYS);
C. birthday = birthday.plus(52, ChronoUnit.WEEKS);
D. birthday = birthday.plus(12, ChronoUnit.MONTHS);
E. birthday = birthday.plus(1, ChronoUnit.YEARS);
F. birthday.plusYears(1);
G. birthday.plus(1, ChronoUnit.YEARS);

Answer. This question investigates some simple rules related to the Gregorian calendar and crucial elements of the Date and Time API added with the release of Java 8.

First, consider the Gregorian calendar. In the options above, you are offered choices involving adding 365 days, 52 weeks, 12 months, and 1 year. Only the last two are reliable, because leap years have 366 days, and no year has a full 52 weeks. So, you can immediately reject options A, B, and C as being incorrect from a logic perspective.

Options D, E, F, and G all look like they attempt to add either 12 months or 1 year, so they look believable from the perspective of business logic. To move forward, though, you must determine if the code is correct from the API perspective.

A key feature of the Date and Time API is that the classes within it (with some exceptions, notably the exceptions it defines) are immutable. This means that, as with the String class, when you invoke a method that changes a date-time element, you do not actually change the contents of the existing object; instead, you create a new object that represents the result of the change.

Therefore, if you wrote this String code

String s = "Hello";
s.toUpperCase();
System.out.println(s);

the output would be Hello because you failed to store the uppercase String HELLO that was created in the second line. The same error is made in options F and G. Both of those options correctly create a date exactly one year in the future—but that object is lost, so the third line of the original code sample would still be operating on today’s date. From this you can determine that options F and G are both incorrect.

You now have only two options left: options D and E. These are both correct from a business logic perspective: They are valid API methods, and the new LocalDate object they create is properly stored for use on the final line. From this you can determine that options D and E must be the correct answers.

However, there’s another wrinkle in this question, which explicitly mentions that it’s August. This has significance; that’s been ignored until now, but the logic given above is not complete without considering this.

When the developer runs the query on her birthday, if that day happens to be February 29 (which means that the day is in a leap year) a decision is forced on the API. Clearly it would be an error if the result were February 29 in the following year because the following year will not be a leap year. Therefore, the API would decide to treat the result as February 28 in the following year. That decision may be arbitrary, but it’s documented and therefore predictable.

That’s why, without a stipulation that precludes “today” from being February 29, even options D and E would be questionable—though they’d still be better choices than the others. If you encounter such a situation in an exam, you would be right to choose them in the absence of any other options.

Conclusion. The correct answers are options D and E.

Source: oracle.com

Monday, March 20, 2023

Go Native with Spring Boot 3 and GraalVM

Introduction


There has been a lot of interest in the past few years about "native Java". You've probably read articles or seen conference talks on the subject.

TL;DR - "native Java"


Traditionally Java programs are compiled to bytecode. At runtime the bytecode is first interpreted and eventually compiled to native machine code by a JVM.

Native Java is the idea of using ahead-of-time (AOT) compilation to produce a native executable or "native image" for a Java application.

A native image can run standalone without relying on a JVM.

This approach potentially offers advantages in terms of:

◉ faster application startup
◉ lower latency
◉ lower memory and CPU footprint and cost

GraalVM is an open source JDK that can compile Java applications to native images, as well as bytecode in the traditional way.

Native Java and Spring

Spring is the most popular Java application framework used today.

Over the last three years, the Spring and GraalVM teams have been working to make it easier for developers to deliver their Spring applications as native images.

Experimental support for native image was achieved via the Spring Native project.

With Spring Boot 3 and Spring 6 (due to be released November 24, 2022), support for native image will be available as a core feature.

Since the release candidate for Spring Boot 3 is now available, this seems a good time to try out the native image support.

Trying it out


So lets have a go at using the Spring Boot 3 release candidate and GraalVM to create a simple web application, compiled to a native executable.

Pre - requisites

You need to have GraalVM and it's native-image tool installed on your machine.

You can download these from here.

Note that you need GraalVM 22.3.0 and the equivalent version of native-image.

I'm using GraalVM Enterprise and native-image 22.3.0

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

The Spring Boot App

The easiest way to get started with building a Spring Boot app is to use the spring initializr.

I'm going to walk through using both Maven and Gradle, so you can pick whichever one you pefer.

You can either enter the configuration by hand, or re use one of:


After choosing the build engine, I selected:

  • Spring Boot: 3.0.0 (RC1)
  • Language: Java
  • Artifact: Vanilla
  • Dependencies:
    • GraalVM Native Support
    • Spring Web

Once you are happy with your configuration click the "GENERATE" button.

Then copy the downloaded file to a directory of your choice, unzip it and cd into the vanilla directory.

Gradle

Start by running the application:

./gradlew bootRun

The application should start in a second or so:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

You can to test the application:

curl localhost:8080

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

404 isn't a good look, so let's add a RestController to say something more meaningful.

Copy Hello.java from the code directory into ./src/main/java/com/example/vanilla (or write your own).

If we run the application (./gradlew bootRun) and test it (curl localhost:8080) again we should see a more meaningful response this time:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

We can also build a stand alone jar:

./gradlew assemble

And run that:

java -jar ./build/libs/vanilla-0.0.1-SNAPSHOT.jar

Again you should see the app start in about a second:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

So now we can take the next step and build our native image without having to make any changes to the project:

./gradlew nativeCompile

This may be a good opportunity to have a tea or coffee as it will take two or three minutes as opposed to a couple of seconds for building a jar (on my Mac, YMMV).

Once the build completes:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

We can run the executable:

./build/native/nativeCompile/vanilla

You should see the application start in around ~0.1 seconds (so about 10x faster).

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

Looking at memory usage on my machine, the native image uses about 1/3 of the memory compared to running the application from the jar.

Maven

Before you start, make sure that $JAVA_HOME is set to point to your GraalVM installation:

$ $JAVA_HOME/bin/java --version
java 17.0.5 2022-10-18 LTS
Java(TM) SE Runtime Environment GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07, mixed mode, sharing)

Then run the application:

./mvnw spring-boot:run

The application should start in a second or so:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

You can use curl localhost:8080 to test the application:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

404 isn't a good look, so let's add a RestController to say something more meaningful.

Copy Hello.java from the code directory into ./src/main/java/com/example/vanilla (or write your own).

If we run the application (./mvnw spring-boot:run) and test it (curl localhost:8080) again we should see a more meaningful response this time:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

We can also build a stand alone jar:

/.mvnw package

And run that:

java -jar ./target/vanilla-0.0.1-SNAPSHOT.jar

Again you should see the app start in about a second.

So now we can take the next step and build our native image without having to make any changes to the project:

./mvnw -Pnative native:compile

This may be a good opportunity as it will take two or three minutes as opposed to a couple of seconds for building a jar (on my Mac, YMMV).

Once the build completes:

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

We can run the executable ./target/vanilla

You should see the application start in around ~0.1 seconds (so about 10x faster).

GraalVM, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Career, Oracle Java Jobs, Oracle Java Skills

Looking at memory usage on my machine, the native image uses about 1/3 of the memory compared to running the application from the jar.

Source: oracle.com

Wednesday, March 15, 2023

Quiz yourself: Deserialization of Java enum types and records


Imagine you are given the following enum and record:

Quiz Yourself, Deserialization of Java Enum, Java Career, Java Skill, Java Tutorial and Materials, Java Job, Java Learning, Java Guides

enum Gender {
  MALE("M"), FEMALE("F"), OTHER;
  Gender() {
    System.out.print("Gender ");
  }
  Gender(String s) {
    System.out.print("GenderS ");
  }
}
record Person(int age, String name, Gender g) implements Serializable {
  Person() {
    this(0, "", Gender.OTHER);
    System.out.print("Person ");
  }
  Person {
    System.out.print("PersonC ");
  }
}

Previously an instance of Person was constructed like the following and serialized to a file:

var v = new Person(60, "John Doe", Gender.MALE);

What might be printed to the console when a program deserializes the contents of the file mentioned above? Choose two.

A. Gender PersonC Person
B. Gender PersonC
C. PersonC Person
D. PersonC
E. Gender Gender Gender Person PersonC
F. GenderS GenderS Gender Person PersonC
G. GenderS GenderS Gender PersonC

Answer. This question investigates the mechanisms of deserialization for both enum types and records.

When a record is deserialized, each of the attributes—in this case the age, name, and gender attributes—must be deserialized to their respective objects or values first. (Note that the age primitive will not create an object.) These attributes are then passed to the canonical constructor of the record. Note that this behavior with records is quite different from deserializing instances of normal classes. No output is printed in handling the int age and the String name attributes, but it’s possible that output might be printed for Gender. You should look at this first.

Whenever code running in a JVM refers to a particular enum value, execution cannot proceed unless that enum value has been initialized. In most cases, the very first reference to one of these values will require several steps to be performed: class loading, class linking, and class preparation. Those three steps must happen in the order listed.

It’s fairly unusual for developers to notice the fact that these steps are separate, but that really doesn’t matter. Class preparation is the step that performs static initialization, and therefore it’s the step that creates the values of the enum itself. This quiz question is unaffected by the earlier steps, so the following discussion will consider the enum to be fully prepared, ignoring the fact that that’s actually the last of several steps. (If you’re interested in this, you can find more in section 12.2 and section 12.3 of the Java Language Specification, which in turn refer to the Java Virtual Machine Specification.)

For this question, you need to know three things.

◉ An attempt to deserialize an object that contains a field of an enum type will make a reference to an enum value.
◉ Any reference to an enum value requires the enum class to be fully prepared.
◉ Therefore, the enum must have been fully prepared—complete with the initialization of all the enum’s constants—before deserialization can proceed.

In view of the above, there are now two distinct possibilities.

First, suppose that at the moment when an instance of Person is deserialized, the enum has not been fully prepared. In this case, it will be necessary to perform that preparation before Person can proceed with deserialization. That initialization includes instantiating all three of the enum’s values. In this situation, expect the three instances to be initialized using their appropriate constructors. (Remember this is the initialization process, not deserialization itself, that is causing this behavior.) So, in this situation, you will see GenderS GenderS Gender as the output, preceding any output referring to Person.

By contrast, if the Gender type has been fully prepared before the moment when an instance of Person is deserialized, the three instances already exist, and no further constructor behavior will be invoked for the Gender type. In such a situation, no messages related to Gender will be output by the deserialization process.

Once the enum is fully prepared, deserialization of the record type can proceed; note that record types differ from other classes in how this happens. A particular difference is that the canonical constructor for a record type is invoked to perform the initialization of the newly allocated object. By contrast, regular serializable classes do not have any constructor invoked, nor is any of the normal instance initialization of the serializable class itself run. (Note, however, that nonserializable parent types will have their default constructors executed during deserialization.)

Given that the canonical constructor is invoked, you will see the message PersonC printed as output. Note that in this case, the canonical constructor is presented in the compact form whereas the argument list is omitted, as is the code that initializes the fields of the record.

Because you might or might not see GenderS GenderS Gender, and you will see PersonC in the output, the two valid answers are

PersonC
GenderS GenderS Gender PersonC

These match options D and G, which tells you that those are the correct options and, by elimination, the other options are incorrect.

If you want to dive more deeply into the serialization process, it is, of course, in the Java specification. See the section that describes the process as it relates to records. In particular, the documentation notes the following:

During deserialization, if the local class equivalent of the specified stream class descriptor is a record class, then first the stream fields are read and reconstructed to serve as the record’s component values; and second, a record object is created by invoking the record’s canonical constructor with the component values as arguments…

 

The process related to enums is documented in this section of the Java specification, which notes

To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments.

 

Notice that the enum deserialization process does not instantiate the values of the enum unless, by necessity, the static initialization process must be completed to complete the deserialization.

As a side note, the descriptions above touched on the idea that there are several steps on the way to class preparation. Most JVMs will perform these steps lazily, simply ensuring that a class is prepared before it’s actually needed. The exact details can vary from one JVM to the next even if the behavior of the developer’s code conforms to the expectations listed in the Java specification.

This lazy approach can be quite beneficial. For example, consider a program with a help system that is built with many classes that are unique to it. If the help system is never used during an execution of that program, those classes might not be needed, and they might never be loaded at all. Further, even if a class is loaded, it might not need to be prepared. Either of these situations can save memory.

To see how this works, try building the following code and observing at what point in the process the MyLazySingleton class is prepared. (Remember that preparation relates to the static initialization.)

class MyLazySingleton {
  static {
    System.out.println("preparing MyLazySingleton");
  }
  private static final MyLazySingleton singleton =
    new MyLazySingleton();
  private MyLazySingleton() {
    System.out.println("instantiating MyLazySingleton");
  }
  public static MyLazySingleton get() {
    return singleton;
  }
}

public class TryStuff {
  public static void main(String[] args) {
    System.out.println("Starting");
    MyLazySingleton mls = null;
    System.out.println("mls variable initialized to null");
    Class<?> cl = MyLazySingleton.class;
    System.out.println("MyLazySingleton.class evaluated.");
    mls = MyLazySingleton.get();
    System.out.println("singleton retrieved");
  }
}

If you run this code under a tracing tool, you’ll probably notice that the file myclasspath/packagename/MyLazySingleton.class is read during the reference to the java.lang.Class object MyLazySingleton.class. However, you’ll also see that the static initialization and, with it, the instantiation of the single instance of MyLazySingleton, does not occur until the code invokes MyLazySingleton.get().

Conclusion. The correct answers are options D and G.

Source: oracle.com

Monday, March 13, 2023

TruffleRuby on OCI Container Instances

Elegance at Speed


Ruby is one of my favourite programming languages. I really appreciate how expressive it is, how productive (and how much fun) it makes development.

Unfortunately Ruby has a reputation for being slow compared to other languages such as JavaScript.

While the performance of the default Ruby interpreter has improved, it still hasn't had the engineering investment that goes into V8.

Friday, March 10, 2023

Quiz yourself: Verifying the operation of stinky Java code


Your colleague is working on an application that analyzes statistics for vehicles of different makes and models. A business rule requirement is that each car instance in the application must have a unique manufacturing year; creating a second instance of a vehicle with the same year must fail.

Quiz yourself, Java code, Oracle Java Career, Java skills, Java Jobs, Java Tutorial and Materials, Java Learning, Java Prep, Java Preparation, Java Certification

The Car class created by the colleague is as follows:

Copy code snippet
Copied to ClipboardError: Could not CopyCopied to ClipboardError: Could not Copy
public class Car {
  Integer year;
  String make;
  String model;
  List<Integer> existingYears = new ArrayList<>();
  public Car(Integer year, String make, String model) {
    this.year = year;
    this.make = make;
    this.model = model;
    existingYears.add(year);
    checkYear(year);
  }
  void checkYear(Integer year) {
    for (int i=0; i < existingYears.size()-1; i++ ) {
      if (existingYears.get(i).equals(year)) {
        throw new IllegalArgumentException("Duplicated year !");
      }
    }
  }
}
public class Car {
  Integer year;
  String make;
  String model;
  List<Integer> existingYears = new ArrayList<>();
  public Car(Integer year, String make, String model) {
    this.year = year;
    this.make = make;
    this.model = model;
    existingYears.add(year);
    checkYear(year);
  }
  void checkYear(Integer year) {
    for (int i=0; i < existingYears.size()-1; i++ ) {
      if (existingYears.get(i).equals(year)) {
        throw new IllegalArgumentException("Duplicated year !");
      }
    }
  }
}

You wonder if the business constraint is properly implemented and decide to test the code using the following code fragment:

new Car(2018, "Honda", "Civic"); // line n1
new Car(2021, "Hyundai", "Accent"); // line n2
new Car(2018, "Ford", "Expedition"); // line n3

Which statement is true? Choose one.

A. The test code runs without throwing an exception.
B. Line n1 will cause IllegalArgumentException.
C. Line n1 will cause IndexOutOfBoundsException.
D. Line n3 will cause IllegalArgumentException.
E. Line n2 will cause IndexOutOfBoundsException.

Answer. This example is perhaps not code you’d be proud of, but you didn’t write it, so there’s no need for embarrassment here.

Look at the code presented and notice that it seems the intention is roughly as follows:

◉ For each call to the constructor, add the proposed list to a list called existingYears.
◉ Call the method checkYear, which performs two things. First it determines if that year has been used before and, if it has, it throws an IllegalArgumentException.

The root problem here is that the existingYears list is created as an instance member of a Car object, and therefore every Car object has a List of its own. Consequently, the years in which other Car objects were created will not be found in each List. The code does not work as intended and will not prevent two separate instances of Car from having the same year value.

If instead the existingYears field carried the static modifier, only one instance List would exist in the program. In that case, the comparison would check the current car’s year with those seen before and line n3 would throw an IllegalArgumentException. That change would make option D correct. But since that wasn’t done, option D is incorrect.

You might notice that the current car’s year is added to the list before the check is run. If you fail to pay close attention to the control of the loop that searches through the list, you might expect that an exception would be thrown for every new car, based on an expected collision with its own year. That would lead you to believe that the code would throw an IllegalArgumentException for any and all attempts to instantiate a Car. In that situation, the first exception would presumably prevent further execution, and you would expect an exception at line n1. However, a more thorough investigation shows that the loop stops before the last item is checked, which is why option B is incorrect.

Notice the following test in the loop:

i < existingYears.size()-1

Because the size value is reduced by one, the code would ignore the last item in the list. That item will be the current car’s year, so the feared collision would not occur. Further, because existingYears is an instance field, the list’s size in the checkYear method is always 1, and the body of the loop never executes at all.

Two of the options suggest that an IndexOutOfBoundsException might be thrown. However, as just noted, the size of the list is always 1 when the checkYear method executes, and the body of the loop will never execute. No code outside that loop attempts to access data from the list using an index; therefore, no code that could possibly throw an IndexOutOfBoundsException will ever be executed. From this you can conclude that options C and E are incorrect.

From the observations above, you can conclude that no exceptions are thrown, and option A must be correct.

Now that the answer has been determined, here are some of the least pleasant aspects of the code—besides the fact that it doesn’t work.

◉ Even though the code for avoiding a collision with the current car’s year is functionally correct, if an attempt is made to add a car that has a duplicate year, existingYears will contain the same year twice. While this would not cause errors in the code shown so far, unexpected behaviors such as this are confusing to programmers trying to make sense of things later, both when they are reading code and when they are debugging. It’s also common for this sort of situation to lead to bugs later when the peculiarity is not thoroughly understood by all. It would be far better to add the year only after it has been approved.

◉ Creating any object with nonprivate mutable fields is generally unwise. It allows changes to be made by any code in the same package. If such changes are made without understanding the implications and, thereby violating some consistency rule pertaining to the data, it’s easy to cause bugs that are hard to track down, especially because such a large amount of code (the entire package or more) might have been responsible for the change.

◉ Checking to see if a Collection contains a given object should be done using the contains method. It’s immediately and unambiguously clear what this method does. By contrast, writing a loop describing how to perform the action leaves the programmer who is reading the code with the responsibility for working out what the code is intended to achieve. Generally speaking, it’s preferable that code have a more declarative nature and a less imperative one. Of course, to be able to use the contains method, you must first ensure that the current car’s year is not added before validation.

◉ Iterating lists using the traditional C-style for loop to control an index for accessing list elements should be avoided. Some lists in Java’s collections (typically those with array backing) iterate tolerably efficiently this way, but if the implementation changes to use a different structure (such as a LinkedList), the resulting efficiency will be relatively or exceedingly poor. If possible, give any data structure the chance to perform its own iteration, since the programmer who created that structure should know best how to iterate the elements efficiently. Instead of using an index-counting loop such as the C-style loop, use Java’s enhanced for loop or extract an Iterator (or perhaps ListIterator, if the situation calls for it) and use that.

Conclusion. The correct answer is option A.

Source: oracle.com

Monday, March 6, 2023

Pseudorandom numbers in Java, Part 2: Randomness with Java 17


Java 17 introduced significant changes to the platform’s pseudorandom number generation capabilities. The first article in this series, “Pseudorandom numbers in Java, Part 1: The Background,” offers a discussion of pseudorandom number generators (PRNGs) and how they work. This article discusses the changes made to PRNGs in Java 17.

Pseudorandom Numbers, Java 17, Oracle Java Tutorial and Materials, Core Java, Java Career, Java Skills, Java Jobs, Java Certification, Java Prep, Java Prepararation

Random number generation was perhaps not given quite enough love in early releases of Java. This inspired a Wikipedia discussion of poor-quality PRNGs that noted

As an illustration, consider the widely used programming language Java. Up until 2020, Java still relied on a linear congruential generator (LCG) for its PRNG, which is of low quality…

Developers have been able to choose between several random generators in Java. The best-known is java.util.Random; another well-known interface, java.lang.Math.random(), simply delegates to java.util.Random.nextDouble().

Java 17 introduced a new interface, java.util.random.RandomGenerator, to consolidate the implementations of existing and new random number generators. The new code is in the java.util.random package, which is also new in Java 17. RandomGenerator has the same methods as java.util.Random, including both the legacy methods and the Java 8 methods that generate Java streams of randoms of various types. For example, for double values, the new interface contains the methods described in Listing 1.

Listing 1. Methods from RandomGenerator with double and DoubleStream values

public default double nextDouble();
    public default double nextDouble(double min);
    public default double nextDouble(double min, double max);
    public default DoubleStream doubles();
    public default DoubleStream doubles(double min, double max);
    public default DoubleStream doubles(long count);
    public default DoubleStream doubles(long count, double min,
        double max);
    public default double nextGaussian();
    public default double nextGaussian(double min, double max);
    public default double nextExponential();

There are similar methods for int and long numeric types. Raw bytes and booleans do not get the same treatment; there is only void nextBytes(byte[]) and boolean nextBoolean().

The legacy classes java.util.Random and java.util.SecureRandom were backfitted to implement this interface, so if you switch the declaration to the new interface, the legacy implementations become interchangeable with the new generators. The legacy classes’ algorithms were not improved, however, to avoid breaking Java’s ever-important backwards compatibility. In fact, given the same seed, the java.util.Random class in both Java 11 and Java 17 will output exactly the same sequence.

In the following example, Random5 is a modified version of the random histogram program shown in Part 1 of this series, which starts with a fixed seed. It should (and does) produce the same stream with both implementations. The withjava command is a script of my own that I use to run Random5 on a particular version of Java.

$ withjava 11 java -Xms4G -Xmx4G Random5.java
Using JAVA_HOME=/usr/local/jdk-11 java -Xms4G -Xmx4G Random5.java in /home/ian/git/Randomness/src/main/java/com/darwinsys/random/15vs17
First 5 random ints are:
1553932502 -2090749135 -287790814 -355989640 -716867186
Generating 100000000 randoms using Random
Generating randoms took 6640 mSec
$ withjava 17 java -Xms4G -Xmx4G Random5.java
Using JAVA_HOME=/usr/local/jdk-17 java -Xms4G -Xmx4G Random5.java in /home/ian/git/Randomness/src/main/java/com/darwinsys/random/15vs17
First 5 random ints are:
1553932502 -2090749135 -287790814 -355989640 -716867186
Generating 100000000 randoms using Random
Generating randoms took 7448 mSec
$

In my tests, over multiple runs, the Java 17 implementation was consistently 10% to 12% slower than the Java 11 version, while generating exactly the same outputs.

SecureRandom has always been about generating better random numbers for use in security-related software. The implementation is intended to meet the requirements specified in section 4.9.1 of the US National Institute of Standards and Technology (NIST) FIPS PUB 140-2, “Security Requirements for Cryptographic Modules,” and the Internet Engineering Task Force (IETF) RFC 4086, “Randomness Requirements for Security.”

Java has two other legacy RandomGenerator implementations: ThreadLocalRandom (from Java 1.7) and SplittableRandom (from Java 1.8). These are listed in the Java 17 RandomGenerator documentation. Of these, ThreadLocalRandom is meant for use in multithreaded designs, and the SplittableRandom class is designed for use in fork/join algorithms where each split should have its own independent PRNG. These two do not meet the security requirements mentioned above; SecureRandom should be used when security is needed.

It turns out that most of the methods of the RandomGenerator interface have default methods coded in terms of nextLong() (the only abstract method in the interface); a complete (but not very usable) implementation of this interface is shown in Listing 2.

Listing 2. A minimal implementation of Java 17’s RandomGenerator

public class CrappyRandom implements RandomGenerator {

    // The attacker has a hard time guessing the
    // exact nanosecond at which this is initialized.
    private long x0 = (21 * System.nanoTime()) & 0xffff;
    private long a = (long) Math.pow(2,7) + 1;
    private long c = 1024;   // The increment
    private long mod = (long) Math.pow(2,32);  // The modulus
    private long lastVal = x0;

    public void setKnuthBadParams() {
        // As per Knuth , to get 7 6 9 0, 7 6 9 0, ...
        mod = 10;
        lastVal = x0 = a = c = 7;
    }

    @Override
    /**
     * This is a crude implementation just to get going.
     * Tests will reveal its quality (or lack thereof).
     */
    public long nextLong() {
        return lastVal = (a * lastVal + c) % mod;
    }
}

Important note: Please do not use this sample code for anything! It produces terribly bad random numbers. It is just here to show that if you can write a decent algorithm, the interface will massage its results to provide all the other types needed to complete the interface.

To avoid the poor behavior of LCG algorithms used on their own, Java 17 introduces a new class of random number algorithms called LXM. The name stands for “LCG + XOR + Mix,” representing the three stages: an LCG, an XOR subgenerator, and a mixer to combine them. The first two stages are based on the best-available LCG values and an exclusive-or (XOR) function; this combination has been shown to provide the best results.

To avoid creating dozens of new public classes for this family of algorithms, Java 17 introduces the new RandomGeneratorFactory class, which allows you to create an implementation of a particular algorithm, as follows:

String algorithm = "L64X1024MixRandom";
RandomGenerator genx = RandomGeneratorFactory.of(algorithm).create();
genx.doubles(1000).doSomethingWithValues();

These algorithms are implemented by nonpublic classes with the same names in the private jdk.random package. For example, the snippet above assigns genx to an instance of jdk.random.L64X1024MixRandom. The list of new algorithms is

  • L32X64MixRandom
  • L32X64StarStarRandom
  • L64X128MixRandom
  • L64X128StarStarRandom
  • L64X256MixRandom
  • L64X1024MixRandom
  • L128X128MixRandom
  • L128X256MixRandom
  • L128X1024MixRandom
  • Xoshiro256PlusPlus
  • Xoroshiro128PlusPlus

The RandomGenerator interface includes a getDefault() default method, which, as expected, returns a usable implementation of the interface without specifying too much about its behavior. In Java 17 on my system, it returns a jdk.random.L32X64MixRandom, but that is not documented and should not be counted on. However, note that getDefault() will be a decent choice unless you need one of the others.

RandomGeneratorFactory also has an all() method that returns a stream of all the providers. Running my simple ShowAllGenerators program displays the following:

Group Name                          Period
      LXM L128X1024MixRandom          Infinity
      LXM L128X128MixRandom         1.15792e+77
      LXM L128X256MixRandom         3.94020e+115
      LXM L32X64MixRandom           7.92282e+28
      LXM L64X1024MixRandom           Infinity
      LXM L64X128MixRandom          6.27710e+57
      LXM L64X128StarStarRandom     6.27710e+57
      LXM L64X256MixRandom          2.13599e+96
   Legacy Random                    2.81475e+14
   Legacy SecureRandom                 0.00000
   Legacy SplittableRandom          1.84467e+19
  Xoshiro Xoshiro256PlusPlus        1.15792e+77
Xoroshiro Xoroshiro128PlusPlus      3.40282e+38

The few algorithms that show Infinity for their period in the output aren’t truly infinite, of course, but they are too large to display in a double. (The RandomGeneratorFactory.period() method actually returns a BigInteger, but I displayed them as double to show the scale instead of a vast run of digits.)

If you know which algorithm you want, you can use the of method, as in the following:

RandomGenerator random = RandomGenerator.of("L128X128MixRandom");

Choosing the best algorithm


The following is excerpted verbatim from the documentation for the java.util.random package, which explains the trade-offs clearly and concisely. So there’s no reason for me to paraphrase it.

There are three groups of random number generator algorithm [sic] provided in Java: the Legacy group, the LXM group, and the Xoroshiro/Xoshiro group.

The legacy group includes random number generators that existed before JDK 17: Random, ThreadLocalRandom, SplittableRandom, and SecureRandom. Random (LCG) is the weakest of the available algorithms, and it is recommended that users migrate to newer algorithms. If an application requires a random number generator algorithm that is cryptographically secure, then it should continue to use an instance of the class SecureRandom.

The algorithms in the LXM group are similar to each other. The parameters of each algorithm can be found in the algorithm name. The number after “L” indicates the number of state bits for the LCG subgenerator, and the number after “X” indicates the number of state bits for the XBG subgenerator. “Mix” indicates that the algorithm uses an 8-operation bit-mixing function; “StarStar” indicates use of a 3-operation bit-scrambler.

The algorithms in the Xoroshiro/Xoshiro group are more traditional algorithms (see David Blackman and Sebastiano Vigna, “Scrambled Linear Pseudorandom Number Generators,” ACM Transactions on Mathematical Software, 2021); the number in the name indicates the number of state bits.

For applications (such as physical simulation, machine learning, and games) that do not require a cryptographically secure algorithm, this package provides multiple implementations of interface RandomGenerator that provide trade-offs among speed, space, period, accidental correlation, and equidistribution properties.

For applications with no special requirements, L64X128MixRandom has a good balance among speed, space, and period, and is suitable for both single-threaded and multi-threaded applications when used properly (a separate instance for each thread).

If the application uses only a single thread, then Xoroshiro128PlusPlus is even smaller and faster, and certainly has a sufficiently long period.

For an application running in a 32-bit hardware environment and using only one thread or a small number of threads, L32X64MixRandom may be a good choice.

For an application that uses many threads that are allocated in one batch at the start of the computation, either a “jumpable” generator such as Xoroshiro128PlusPlus or Xoshiro256PlusPlus may be used, or a “splittable” generator such as L64X128MixRandom or L64X256MixRandom may be used.

For an application that creates many threads dynamically, perhaps through the use of spliterators, a “splittable” generator such as L64X128MixRandom or L64X256MixRandom is recommended. If the number of generators created dynamically may be very large (millions or more), then using generators such as L128X128MixRandom or L128X256MixRandom, which use a 128-bit parameter rather than a 64-bit parameter for their LCG subgenerator, will make it much less likely that two instances use the same state cycle.

For an application that uses tuples of consecutively generated values, it may be desirable to use a generator that is k-equidistributed such that k is at least as large as the length of the tuples being generated. The generator L64X256MixRandom is provably 4-equidistributed, and L64X1024MixRandom is provably 16-equidistributed.

For applications that generate large permutations, it may be best to use a generator whose period is much larger than the total number of possible permutations; otherwise it will be impossible to generate some of the intended permutations. For example, if the goal is to shuffle a deck of 52 cards, the number of possible permutations is 52! (52 factorial), which is larger than 2225 (but smaller than 2226), so it may be best to use a generator whose period is at least 2256, such as L64X256MixRandom or L64X1024MixRandom or L128X256MixRandom or L128X1024MixRandom. (It is of course also necessary to provide sufficiently many seed bits when the generator is initialized, or else it will still be impossible to generate some of the intended permutations.)

There are other interesting methods in the factory class; running javap (or looking at the documentation) reveals them all.

$ javap java.util.random.RandomGeneratorFactory
Compiled from "RandomGeneratorFactory.java"
public final class RandomGeneratorFactory<T extends RandomGenerator> {
  static <T extends RandomGenerator> T of(String, Class<T>) throws IllegalArgumentException;
  static <T extends RandomGenerator> RandomGeneratorFactory<T> factoryOf(String, Class<T>) throws IllegalArgumentException;
  public static <T extends RandomGenerator> RandomGeneratorFactory<T> of(String);
  public static RandomGeneratorFactory<RandomGenerator> getDefault();
  public static java.util.stream.Stream<RandomGeneratorFactory<RandomGenerator>> all();
  public String name();
  public String group();
  public int stateBits();
  public int equidistribution();
  public java.math.BigInteger period();
  public boolean isStatistical();
  public boolean isStochastic();
  public boolean isHardware();
  public boolean isArbitrarilyJumpable();
  public boolean isJumpable();
  public boolean isLeapable();
  public boolean isSplittable();
  public boolean isStreamable();
  public boolean isDeprecated();
  public T create();
  public T create(long);
  public T create(byte[]);
}
$

The informational methods (such as isHardware() and isJumpable()) are in the RandomGeneratorFactory but not in the RandomGenerator instances themselves. The full list of informational methods is shown in Table 1.

Table 1. Informational methods in RandomGeneratorFactory

Name Meaning 
isStatistical() Returns true if the generated values are computed using an algorithm that is statistically deterministic.
isStochastic()  Returns true if the generated values are computed using some external or entropic input(s).
isHardware()  Returns true if the random number generator (RNG) is implemented as a hardware random number generator (HRNG). 
isJumpable()  Returns true if the RNG can jump ahead in the cycle of random data. 
isArbitrarilyJumpable()  Returns true if the RNG can jump arbitrarily far into the cycle of data. 
isLeapable()  Returns true if the RNG can jump to a very distant point in the cycle. 
isSplittable()  Returns true if the RNG can be cloned to make a second generator with the same properties but positioned further in the cycle. 
isStreamable();  Returns true if the RNG can output into a Java stream. 
isDeprecated()  Returns true if the implementation of RandomGenerator has been marked for deprecation. 

Suppose you wanted to determine which RandomGenerator instances are streamable, that is, which can emit random values as a stream.

// From ShowStreamableGenerators
RandomGeneratorFactory.all()
    .filter(rgFactory -> rgFactory.isStreamable())
    .map(rgFactory-> rgFactory.name())
    .sorted()
    .forEach(System.out::println);

As you might expect, all the new PRNGs are streamable, and all the legacy ones are not.

Testing randomness


Two of the main test suites for RNGs are TestU01 and PractRand.

TestU01, by Pierre L’Ecuyer and Richard Simard at the University of Montreal, includes a 200-page user manual that is highly recommended for those who are mathematically oriented; it’s useful even if you don’t plan to use the test suite. The authors note the following, in passing:

There is no universal test or battery of tests that can guarantee, when passed, that a given generator is fully reliable for all kinds of simulations. Passing many tests improves one’s confidence in the RNG, although it never proves that the RNG is foolproof. In fact, no RNG can pass every conceivable statistical test. One could say that a bad RNG is one that fails simple tests, and a good RNG is one that fails only very complicated tests that are extremely hard to find or impractical to run.

The LXM algorithms have been tested against both TestU01 and PractRand, and they passed. The work for Java 17 was done under JEP 356, Enhanced pseudo-random number generators, which contains more information on the choices made, the tests performed and passed, and so on.

Performance


If you need only a few random numbers, performance isn’t a big deal. But if you need millions or billions, speed matters. To measure the relative performance of each generator, I wrote a simple timing loop called TimeAllGenerators. I generated 10 million randoms with each generator to get measurable values.

dalai-LAPTOP-random $ java TimeAllGenerators.java | sort -k5n
Generating 10000000 randoms took 55 mSec using SplittableRandom
Generating 10000000 randoms took 56 mSec using Xoroshiro128PlusPlus
Generating 10000000 randoms took 57 mSec using L64X128MixRandom
Generating 10000000 randoms took 57 mSec using L64X128StarStarRandom
Generating 10000000 randoms took 58 mSec using L32X64MixRandom
Generating 10000000 randoms took 59 mSec using Xoshiro256PlusPlus
Generating 10000000 randoms took 63 mSec using L64X1024MixRandom
Generating 10000000 randoms took 65 mSec using L64X256MixRandom
Generating 10000000 randoms took 76 mSec using L128X128MixRandom
Generating 10000000 randoms took 77 mSec using L128X256MixRandom
Generating 10000000 randoms took 89 mSec using L128X1024MixRandom
Generating 10000000 randoms took 207 mSec using Random
Generating 10000000 randoms took 2571 mSec using SecureRandom

As you can see, the legacy implementation java.util.Random takes around four times as long as the more modern algorithms and gives less-reliable randomness. SecureRandom is 10 times slower but gives good randomness for security-based applications. If you need to generate a lot of random numbers, take this into account. If you need only a small number of randoms, any of the generators will do.

Migrating to the new APIs


Should you change to a newer API? Migrate if good random performance and reliability is important. If you are just using one or two random values, it may not be worthwhile. Otherwise, yes; you should migrate.

The first step in migrating would be to change

Random random = new Random();

to

RandomGenerator random = new Random();

Then, at least you are depending on the interface. A better option would be to change to the following:

RandomGenerator random = RandomGenerator.default();

Then, you would be using one of the newer implementations, which is chosen for you.

If you want to ask for a particular implementation, based on the performance figures I generated and on the notes on choosing a generator given previously, use the following:

RandomGenerator random = RandomGenerator.of("L64X1024MixRandom");

Finally, consider moving the code to a streams approach, since you now have a stream-capable random generator. Note that migrating iterative code to streams is a topic for another article. For that, I’ll refer you to Raoul-Gabriel Urma’s article, “Processing data with Java SE 8 streams, Part 1.”

In summary, Java 17 provides a new framework for building multiple implementations of PRNGs that provide a standard interface and whose implementations may more readily be exchanged. While the legacy implementations are not changed and still use LCGs, the new ones use the best-known LCG parameters and combine these with an XOR operation to be more random.

My final advice: For general use, if you want good randomness, use RandomGenerator.default() in new code, and consider migrating to that while maintaining legacy code. For security work, continue to use SecureRandom.

Source: oracle.com