Table of Contents
- 2.1 Restore Always-Strict Floating-Point Semantics
- 2.2 Pattern Matching for Switch (Preview)
- 2.3 Sealed Classes
- 2.4 Enhanced Pseudo-Random Number Generators
- 2.5 Deprecate the Applet API for Removal
- 2.6 Strongly Encapsulate JDK Internals
- 2.7 Remove RMI Activation
- 2.8 Remove the Experimental AOT and JIT Compiler
- 2.9 Conclusion
2.1. Restore Always-Strict Floating-Point Semantics
This JEP is targeted towards scientific calculations which involve
floating-point numbers. To guarantee the same calculation result on all
platforms, we have been using the keyword strictfp
.
The strictfp modifier accomplishes this by representing all intermediate values as IEEE single-precision and double-precision values. But due to hardware heating issues, it became optional in JDK 1.2.
Today, as the hardwares have evolved and those heating issues are fixed, default floating-point semantics have been changed to consistently strict.
With this change, we do not need to use strictfp keyword, anymore.
2.2. Pattern Matching for Switch (Preview)
This change adds pattern matching for switch statements and expressions. Since this is a preview feature, we need to use –enable-preview option to enable it.
2.2.1. Better instanceof
checking with Switch Statements
Traditionally, if we had to write a code that checks the instance type and perform some logic, it was the way
Traditional way of checking instance type Object o; if (o instanceof String) { String s = (String) o; String.format("String %s", s) } else if (o instanceof Integer) { Integer i = (Integer) o; String.format("int %d", i) } else if (o instanceof Double) { Double d = (Double) o; String.format("double %f", d) }
In Java 17, we can rewrite the whole expression in the following manner;:
New Approach of Type Checking Object o; switch (o) { case Integer i -> String.format("int %d", i); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); }
2.2.2 Handling null
Values
Traditional switch
statements throw NullPointerException
if the selector expression evaluates to null
.
From Java 17, we can check for null values as the separate case itself.:
Before Java 17if (s == null) { System.out.println("oops!"); return; } switch (s) { case "Foo", "Bar" -> System.out.println("Great"); default -> System.out.println("Ok"); }
In Java 17
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
In Java 17
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
2.2.3. No explicit null
checks are needed
Traditional switch
statements throw NullPointerException
if the selector expression evaluates to null
. With this change, we can check for such null expressions as the separate case itself.:
Before Java 17 if (s == null) { System.out.println("oops!"); return; } switch (s) { case "Foo", "Bar" -> System.out.println("Great"); default -> System.out.println("Ok"); }
In Java 17
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
In Java 17
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
2.2.4. Improved instanceof
checking
Before Java 16, if we had to write a code that checks the instance type and perform some logic, it was the way:
Before Java 16 Object o; if (o instanceof String) { String s = (String) o; String.format("String %s", s) } else if (o instanceof Integer) { Integer i = (Integer) o; String.format("int %d", i) } else if (o instanceof Double) { Double d = (Double) o; String.format("double %f", d) }
Java 17 takes it to the next level with switch expression. Now we can rewrite the above code as:
In Java 17 Object o; switch (o) { case Integer i -> String.format("int %d", i); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); }
While the above changes have been made, it has been ensured that all of the oldswitch expressions must continue to work.
You can see all the changes to switch expressions in the JEP webpage.
While the above changes have been made, it has been ensured that all of the oldswitch expressions must continue to work.
You can see all the changes to switch expressions in the JEP webpage.
2.3. Sealed Classes
In Java, by default, there has been no restriction on a class which
public interfaces it can implement. From Java 15, we can declare a
class or interface sealed class or sealed interface using the modifier sealed.
Sealed classes and interfaces restrict which other classes or
interfaces may extend or implement them.:
public sealed class Account
permits CurrentAccount, SavingAccount, LoanAccount {
}
It was a preview feature in Java 15 and Java 16. In Java 17, it has
become a
standard feature with no changes to what was available in Java
16.
public sealed class Account
permits CurrentAccount, SavingAccount, LoanAccount {
}
2.3.1. sealed
modifier and permits
keyword
The sealing of a class or interface can be done using the
modifier sealed
in the declaration time. Then, after any extends
and implements
clauses, the permits
clause specifies the classes that are permitted to extend the
sealed class.
The reserved keyword permits
lists all the classes that can extend the sealed class directly.
The listed subclasses can either be final
, non-sealed, or sealed.
For example, the following declaration of Account
specifies three permitted subclasses:
sealed class Account
permits CurrentAccount, SavingAccount, LoanAccount {
}
final class CurrentAccount extends Account {}
non-sealed class SavingAccount extends Account {}
sealed class LoanAccount extends Account permits HomeloanAccount, AutoloanAccount {}
final class HomeloanAccount extends LoanAccount{}
final class AutoloanAccount extends LoanAccount{}
Here, we are permitted only 3 classes the permission to extend
the Account
class. No other class class can extend the Account
class.
Similarily, only HomeloanAccount
and AutoloanAccount
are only two supported account types for LoanAccount
.
sealed
in the declaration time. Then, after any extends
and implements
clauses, the permits
clause specifies the classes that are permitted to extend the
sealed class.
permits
lists all the classes that can extend the sealed class directly.
The listed subclasses can either be final
, non-sealed, or sealed.
Account
specifies three permitted subclasses:
sealed class Account
permits CurrentAccount, SavingAccount, LoanAccount {
}
final class CurrentAccount extends Account {}
non-sealed class SavingAccount extends Account {}
sealed class LoanAccount extends Account permits HomeloanAccount, AutoloanAccount {}
final class HomeloanAccount extends LoanAccount{}
final class AutoloanAccount extends LoanAccount{}
Account
class. No other class class can extend the Account
class.
HomeloanAccount
and AutoloanAccount
are only two supported account types for LoanAccount
.
2.3.2 final
, sealed
and non-sealed
Subclasses
The permitted subclasses must have exactly one of the following
modifiers to describe how it continues the sealing initiated by its
superclass:
-
final class cannot be extended further.
-
sealed class can only be extended by its permitted subclasses.
-
non-sealed class can be extended by unknown subclasses as well. A sealed
class cannot force the sealing behavior to its permitted subclasses.
2.3.4 Packaging of sealed classes
-
The sealed class and its subclasses, all must be inside the same
module.
-
If the given module is a unnamed module then all the classes must be
inside the same package.
-
If the permitted classes are written in the same
.java
file then we can omit the permits
keyword.
-
Account.java
public sealed class Account {} //Omits 'permits' keyword
final class CurrentAccount extends Account {}
non-sealed class SavingAccount extends Account {}
sealed class LoanAccount extends Account {} //Omits 'permits' keyword
final class HomeloanAccount extends LoanAccount{}
final class AutoloanAccount extends LoanAccount{}
.java
file then we can omit the permits
keyword.
Account.java public sealed class Account {} //Omits 'permits' keyword final class CurrentAccount extends Account {} non-sealed class SavingAccount extends Account {} sealed class LoanAccount extends Account {} //Omits 'permits' keyword final class HomeloanAccount extends LoanAccount{} final class AutoloanAccount extends LoanAccount{}
2.3.4. Records as sealed classes
We can have a record class in the permits
clause of a sealed
class or interface.
Record classes are implicitly final
and can be used as subclasses of a sealed class.
permits
clause of a sealed
class or interface.
final
and can be used as subclasses of a sealed class.
2.3.4. APIs Support
Java has added two new methods to support the usage of sealed classes::
java.lang.constant.ClassDesc[] permittedSubclasses() - It returns an array of ClassDesc objects where each object in array represent a subclass of the sealed class.
boolean isSealed() – It returns true if the given class or interface is sealed.
java.lang.constant.ClassDesc[] permittedSubclasses() - It returns an array of ClassDesc objects where each object in array represent a subclass of the sealed class.
boolean isSealed() – It returns true if the given class or interface is sealed.
2.3.5. Sealed Interfaces
There are not many differences between how a sealed class is defined vs.
how a sealed interface is defined. Like sealed classes, to seal an
interface, add the sealed
modifier to its declaration.
Then, after any extends
clause, add the permits
clause which specifies the classes that can implement the sealed
interface and the interfaces that can extend the sealed interface.:
IReport.java
public sealed interface IReport
permits Printable, Formattable, ExcelReport, PdfReport {
}
non-sealed interface Printable extends IReport {}
non-sealed interface Formattable extends IReport {}
non-sealed class ExcelReport implements IReport {}
non-sealed class PdfReport implements IReport {}
Sealed interfaces follow the same declaration rules as sealed classes.
sealed
modifier to its declaration.
extends
clause, add the permits
clause which specifies the classes that can implement the sealed
interface and the interfaces that can extend the sealed interface.:
IReport.java
public sealed interface IReport
permits Printable, Formattable, ExcelReport, PdfReport {
}
non-sealed interface Printable extends IReport {}
non-sealed interface Formattable extends IReport {}
non-sealed class ExcelReport implements IReport {}
non-sealed class PdfReport implements IReport {}
2.3.6 When to use sealed classes?
Remember the use of instanceof operator to test whether the
object is an instance of the specified class. Generally, we checked all
the possible class types and, at last, in else
block, we have to write some code if the given instance doesn’t
match any class type.
Also, there is no guarantee that another developer will add one more
class to the hierarchy and add the type check in the given instanceof
checks. Sealed classes can impose this restriction on the
hierarchies at the language level.
With the future direction of instanceof
operator and pattern matching, the default
block or else
block will not be needed.
As soon as a new subclass is added to a sealed class, by using type
test patterns, the compiler will be able to check that every permitted
subclass is covered.
else
block, we have to write some code if the given instance doesn’t
match any class type.
instanceof
checks. Sealed classes can impose this restriction on the
hierarchies at the language level.
instanceof
operator and pattern matching, the default
block or else
block will not be needed.
2.4. Enhanced Pseudo-Random Number Generators
2.4.1. RandomGenerator
This JEP introduced a new interface called RandomGenerator
which aims to make future pseudorandom number generator (PRNG)
algorithms easier to implement or use.
With the legacy PRNG classes Random, ThreadLocalRandom, and SplittableRandom, it is difficult to replace any of them in an application with some other algorithm because they did not have any supertype to support runtime changes.
With the introduction of RandomGenerator
an interface, we can inject any implementing generator class where
the client uses RandomGenerator
type in the application code. The new classes are:
- SplittableGenerator – can be split into two objects (the original one and a new one) each of which obeys that same protocol.
- JumpableGenerator – can easily jump forward, by a moderate amount.
- LeapableGenerator – can easily not only jump but also leap forward, by a large amount.
- ArbitrarilyJumpableGenerator – can easily jump forward, by an arbitrary amount, to a distant point in the state cycle.
- StreamableGenerator – augments the RandomGenerator interface to provide methods that return streams of RandomGenerator objects.
Also, legacy random classes, such as Java.util.Random, SplittableRandom, ThreadLocalRandom and SecureRandom now extend the new RandomGenerator interface.
2.4.2. RandomGeneratorFactory
The RandomGeneratorFactory provides methods for selecting random number generator algorithms. We can choose a particular algorithm by its name and use it for generating random numbers.
The default algorithm is L32X64MixRandom. Following is the list of all supported algorithms:
- L128X1024MixRandom
- L128X128MixRandom
- L128X256MixRandom
- L32X64MixRandom
- L64X1024MixRandom
- L64X128MixRandom
- L64X128StarStarRandom
- L64X256MixRandom
- Random (Legacy)
- SecureRandom (Legacy)
- SplittableRandom (Legacy)
- Xoroshiro128PlusPlus
- Xoshiro256PlusPlus
The Legecy
group represents the old PRNGs. Please note that none of the new
implementations are thread-safe while both Java.util.Random and Java.security.SecureRandom is.:
RandomGeneratorFactory factory = RandomGeneratorFactory.of("SecureRandom") RandomGenerator random = factory.create(200L); //get random numbers randomGenerator.nextDouble();
2.5. Deprecate the Applet API for Removal
Most of the web browsers have already removed the support for Applets due to security concerns. In Java 9, Applet API was marked deprecated.
Since it has become irrelevant today, Java 17 has marked it for removal.
2.6. Strongly Encapsulate JDK Internals
This JEP strongly encapsulates all internal elements of the JDK, except for critical internal APIs such as sun.misc.Unsafe.
From Java 9 to Java 16, developers were able to access the JDK internal
APIs using the flag –illegal-access
. Java 17 will ignore the flag, and if the flag is present.
The console will display a message informing the discontinuation of the flag.
2.7. Remove RMI Activation
Remove the Remote Method Invocation (RMI) Activation mechanism while preserving the rest of RMI. It is obsolete and has been deprecated in Java 15.
2.8. Remove the Experimental AOT and JIT Compiler
This JEP removes the experimental Java-based ahead-of-time (AOT) and just-in-time (JIT) compiler introduced in Java 9 and Java 10.
The Graal compiler was made available as an experimental JIT compiler in JDK 10 via JEP 317. Since they were introduced, there was little use of these experimental features, and the effort required to maintain and enhance them was significant.
The developers can still leverage these features using GraalVM.
2.9. Conclusion
Java 17 has lots of exciting features and a long road of support and commitment. It removes many of the already deprecated APIs from Java.
There is significantly less chance to find any applications which are still using those deprecated features, still, developers must be careful to thoroughly check the application code and dependencies before migrating to Java 17.
As Java 17 is an LTS release, and major popular frameworks (e.g., Spring 6 and Spring Boot 3) will be supporting the new features, it is better to plan for Java 17 migration.