How to Build a Food...
December 18, 2024
For more than 25 years, Java has empowered developers to design and build the next generation of robust, scalable, and secure applications,” said Georges Saab, senior vice president of development, Java Platform and chair, OpenJDK Governing Board, Oracle. “The innovative new enhancements in Java 20 reflect the vision and invaluable efforts the global Java community has contributed throughout Java’s existence. With the support provided by Oracle’s ongoing Java technology leadership and community stewardship, Java has never been more relevant as a contemporary language and platform that helps developers improve productivity.”
Oracle on March 21, 2023, announced the availability of Java 20, the latest version of the world’s number one programming language and development platform. Java 20 (Oracle JDK 20) delivers thousands of performance, stability, and security improvements, including platform enhancements that will help developers improve productivity and drive innovation and growth across their organizations
These are the features that made into Java 20 release are incubator or preview features. These are features that still need to be completed and must be explicitly activated (with –enable-preview in the java and javac commands) in order to be able to test them.
The Vector API was first proposed by JEP 338 in JDK 16, and then it was re-incubated in JDK17, JDK18, JDK19 and JDK20.
To explain how the Java Vector API abstraction works, we need to explore different CPU architectures and provide a basic understanding of data-parallel computation. In contrast to a regular computing operation, as in 1+1, where two “pieces of data” are added in one operation, a data-parallel operation is executing a simple operation (e.g., +) on multiple “pieces of data” at the same time. This mode of operation is called SIMD (Single Instruction, Multiple Data), whereas the traditional way of execution is called SISD (Single Instruction, Single Data). The performance speed-up results from applying the same operation on more than one “piece of data” within one CPU cycle. As a simple example: Instead of adding each element of an array A with each element of an array B, we take chunks of array A and array B and operate simultaneously. The two modes are illustrated below and should provide evidence of why SIMD should increase computational performance.
Here is simple example, we want to find hypotenuse value of a triangle:
The same code converted into Vector API looks like this:
To explain this, we are dividing array into small parts and finding the hypotenuse of all values together. AVX compatible CPUs can handle 256 Bits while AVX-512 can provide 512 bits of data-width. One operation usually takes 32 Bits of data-width, so if we take 256 Bits of CPU and 32 Bits of data, we can perform same operation on 256/32=8 values of an array. But it changes CPU by CPU, here “FloatVector.SPECIES_PREFERRED” provides that value. Then we are dividing array into 8 parts, until we can. But last part of an array can be between 1-7 too, which cannot be grouped to 8, so we have to perform that part manually. There is another option mask, which checks that array size can be grouped or not and we can pass that mask value in “FloatVector.fromArray” and “FloatVector.IntoArray” method like below code, but it is less performant:
This feature is yet not added in latest IDEs, so you have to add vector APIs’ module using below command:
–add-modules=jdk.incubator.vector
Scoped values allow a value (i.e., any object) to be stored for a limited time in such a way that only the thread that wrote the value can read it. If multiple threads use the same ScopedValue field, it may contain a different value from the point of view of each thread.
If you are familiar with ThreadLocal variables, this will sound familiar. In fact, scoped values are a modern alternative for thread locals. In ThreadLocal, it stores value in a map, with Thread being the key and value being the values, so values are stored until that thread dies or we remove it from map using .remove() method, otherwise the value will be stored in map forever, which can cause memory leak and new threads are being created with the current thread so all values from current thread will be duplicated, hence it can create memory footprint. ScopedValues solve this problem.
In ScopedValues, we can pass that object value in “ScopedValue.where(…)” and then we can pass runnable method in “.run()” like below:
Scoped values are usually created as public static fields like above example, so they can be retrieved from any method without us having to pass them as parameters to those methods.
This is Java20 Early Access Release, so to run this program you need to give following parameters:
Record patterns were first introduced in Java 19. A record pattern can be used with instance of or switch to access the fields of a record without casting and calling accessor methods.
Here is a simple sample record:
Using a record pattern, we can now write an instance of expression as follows:
Till now, we could do it like this:
Starting with Java 20, we can also specify a record pattern in the for loop and then access x and y directly like this:
“Pattern Matching for Switch” was first introduced in Java 17 and allows us to write a switch statement like the following:
This way, we can use a switch statement to check whether an object is of a specific class (and, if necessary, satisfies additional conditions) and cast this object simultaneously and implicitly to the target class. We can also combine the switch statement with record patterns to access the record fields directly.
With JDK Enhancement Proposal 433, the following changes were made in Java 20:
An exhaustive switch (i.e., a switch that includes all possible values) throws a MatchException (rather than an IncompatibleClassChangeError) if it is determined at runtime that no switch label matches.
That can happen if we subsequently extend the code but only recompile the changed classes. The best way to show this is with an example:
Using the Position record from the “Record Patterns” chapter, we define a sealed interface Shape with the implementations Rectangle and Circle:
In addition, we write a ShapeDebugger that prints different debug information depending on the Shape implementation:
Since the compiler knows all possible implementations of the sealed Shape interface, it can ensure that this switch expression is exhaustive. We call the ShapeDebugger with the following program:
We compile the code and run the main class. Then we add another shape Oval, add it to the permits list of the Shape interface, and extend the main program:
If we do this in an IDE, it will immediately tell us that the switch statement in the ShapeDebugger does not cover all possible values. However, if we work without an IDE, recompile only the changed classes and then start the main program, the following happens:
The Java Runtime Environment throws a MatchException because the switch statement in the ShapeDebugger has no label for the Oval class. The same can happen with an exhaustive switch expression over the values of an enum if we subsequently extend the enum.
Good Read: Popular Java Frameworks to Learn in 2022
As with the previously discussed record patterns with instance of, the compiler can now also infer the type arguments of generic records in switch statements.
Previously, we had to write a switch statement (based on the example classes from the “Record Patterns” chapter) as follows:
Starting with Java 20, we can omit the <String> type arguments inside the switch statement:
Hence, to conclude Java20 offers mainly with “scoped values,” we get a very useful construct in Java 20 to provide a thread and possibly a group of child threads with a read-only, thread-specific value during their lifetime.
All other JEPs are minimally (or not at all) modified resubmissions of previous JEPs.
Java’s ability to boost performance, stability, and security continues to make it the world’s most popular programming language.