Table of Contents
15.1 Sealed Classes and Interfaces (Preview)
Prior to Java 15, there was no restriction on classes or interfaces regarding which all classes can inherit them. A public interface was available to be implemented by any class, and any public class was available to be extended by any other class – unless declared final.
Now with Java 15, a class or an interface can be declared sealed class or sealed interface using the modifier sealed. It is a preview feature in Java 15 through JEP 360.
A sealed class or interface restricts which other classes or interfaces may extend or implement them. Conceptually, it is a more declarative way than access modifiers to restrict the use of a class or interface as a parent class or as a parent interface.
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
.
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{}
15.2 Edwards-Curve Digital Signature Algorithm (EdDSA)
Cryptography related stuff, Java 15 implements an additional digital signature scheme using the Edwards-Curve Digital Signature Algorithm (EdDSA) as described by RFC 8032. The EdDSA signature scheme is popular due to its improved security and performance (faster) over other signature schemes, and it is also one of the signatures schemes that are allowed in TLS 1.3.
15.3 Hidden Classes
import java.nio.charset.StandardCharsets; import java.security.*; import java.util.Base64; public class JEP339 { public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519"); KeyPair kp = kpg.generateKeyPair(); byte[] msg = "abc".getBytes(StandardCharsets.UTF_8); Signature sig = Signature.getInstance("Ed25519"); sig.initSign(kp.getPrivate()); sig.update(msg); byte[] s = sig.sign(); System.out.println(Base64.getEncoder().encodeToString(s)); } }
This JEP introduces hidden classes that are not discoverable and have a limited lifecycle (shorter live), good for developers that generate classes dynamically at runtime. And now we can use this new Lookup::defineHiddenClass API to create a hidden class or interface from bytes.
Example code to use defineHiddenClass
to create a hidden class from a Base64 encoded class, and launch
the static lookup
method manually.
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Base64;
public class LookupProxyTest {
//Here is the Base64 encoded class.
/
public class LookUpProxy{
public static Integer lookup() {
return 1;
}
}*/
static final String CLASS_IN_BASE64 =
"yv66vgAAADcAFQoABAANCgAOAA8HABAHABEBAAY8aW5pdD4BAAMoKV" +
"YBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAGbG9va3VwAQAVKClM" +
"amF2YS9sYW5nL0ludGVnZXI7AQAKU291cmNlRmlsZQEAEExvb2tVcF" +
"Byb3h5LmphdmEMAAUABgcAEgwAEwAUAQAkY29tL21reW9uZy9qYXZh" +
"MTUvamVwMzcxL0xvb2tVcFByb3h5AQAQamF2YS9sYW5nL09iamVjdA" +
"EAEWphdmEvbGFuZy9JbnRlZ2VyAQAHdmFsdWVPZgEAFihJKUxqYXZh" +
"L2xhbmcvSW50ZWdlcjsAIQADAAQAAAAAAAIAAQAFAAYAAQAHAAAAHQ" +
"ABAAEAAAAFKrcAAbEAAAABAAgAAAAGAAEAAAADAAkACQAKAAEABwAA" +
"AB0AAQAAAAAABQS4AAKwAAAAAQAIAAAABgABAAAABgABAAsAAAACAAw=";
public static void main(String[] args) throws Throwable {
testHiddenClass();
}
// create a hidden class and run its static method
public static void testHiddenClass() throws Throwable {
byte[] classInBytes = Base64.getDecoder().decode(CLASS_IN_BASE64);
Class<?> proxy = MethodHandles.lookup()
.defineHiddenClass(classInBytes,
true, MethodHandles.Lookup.ClassOption.NESTMATE)
.lookupClass();
System.out.println(proxy.getName());
MethodHandle mh = MethodHandles.lookup().findStatic(proxy,
"lookup",
MethodType.methodType(Integer.class));
Integer status = (Integer) mh.invokeExact();
System.out.println(status);
}
}
Output :
com.java15.jep371.LookUpProxy/0x0000000800b94440 1
This JEP also deprecated the Unsafe.defineAnonymousClass
API and marked it for removal in the future. Please don’t use
this API anymore.
byte[] classInBytes = Base64.getDecoder().decode(CLASS_IN_BASE64);
Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe)theUnsafe.get(null);
// @Deprecated(since = "15", forRemoval = false)
Class<?> proxy = unsafe.defineAnonymousClass(
LookupProxyTest.class, classInBytes, null);
15.4 Remove the Nashorn JavaScript Engine
- Java 8 JEP 174 introduced Nashorn as a replacement for the Rhino Javascript engine.
-
Java 11 JEP 335 deprecated the Nashorn JavaScript Engine
and
jjs
tool. -
Now, Java 15 removed the Nashorn JavaScript Engine and
jjs
tool permanently.
This JEP also removed the below two modules:
-
jdk.scripting.nashorn
– contains thejdk.nashorn.api.scripting
andjdk.nashorn.api.tree
packages. -
jdk.scripting.nashorn.shell
– contains the jjs tool.
15.5 Pattern Matching for instanceof (Second Preview)
Java 14 JEP 305 introduced Pattern Matching as a preview feature. This JEP is a second preview of the pattern matching to gain additional feedback, with no change to the API.
A typical instanceof-and-cast
to check the object’s type and cast it.
private static void print(Object obj) {
if (obj instanceof String) { // instanceof
String s = (String) obj; // cast
if ("java15".equalsIgnoreCase(s)) {
System.out.println("Hello Java 15");
}
} else {
System.out.println("not a string");
}
}
For pattern matching, we can check, cast, and bind in a single line.
private static void printWithPatternMatching(Object obj) {
// instanceof, cast and bind variable in one line.
if (obj instanceof String s) {
if ("java15".equalsIgnoreCase(s)) {
System.out.println("Hello Java 15");
}
} else {
System.out.println("not a string");
}
}
15.6 Records (Second Preview)
Java 14 JEP 359 introduced the records as a preview feature. This JEP enhanced the records with features like support sealed types, local records, annotation on records, and Reflection APIs for records.
Records and Sealed Types
public sealed interface Fruit permits Apple, Orange {}
record Apple() implements Fruit{}
record Orange() implements Fruit{}
Local Records – records declare inside a method.
The below code snippet use local record to improves the readability of the stream operations.
List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {
// Local record
record MerchantSales(Merchant merchant, double sales) {
}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(toList());
}
private double computeSales(Merchant merchant, int month) {
// some business logic to get the sales...
return ThreadLocalRandom.current().nextDouble(100, 10000);
}
Annotations on records
public record Game(@CustomAnno Rank rank) { ... }
Reflection API
The java.lang.Class
added two public methods:
-
RecordComponent[] getRecordComponents()
-
boolean isRecord()
15.7. JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector
Java 12 JEP 189 introduced the Shenandoah garbage collector as an experimental feature, and now become a product feature in Java 15.
Before Java 15, we need -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
to enable the Shenandoah GC.
java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
In Java 15, we only need -XX:+UseShenandoahGC
to enable the Shenandoah GC.
java -XX:+UseShenandoahGC
However, the official OpenJDK 15 build didn’t include the Shenandoah GC (just like what happened in Java 12). Read this story – Not all OpenJDK 12 builds include Shenandoah: Here’s why.
$ java -XX:+UseShenandoahGC ClassName
Error occurred during initialization of VM
Option -XX:+UseShenandoahGC not supported
$ java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)
To try Shenandoah GC, we need other JDK builds like AdoptOpenJDK.
$ java -XX:+UseShenandoahGC ClassName
$ java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment AdoptOpenJDK (build 15+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15+36, mixed mode, sharing)
P.S The default garbage collector remains G1.
Further Reading
- JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector
15.8. Remove the Solaris and SPARC Ports
Java 14 JEP 362 deprecated the Solaris/SPARC, Solaris/x64, and Linux/SPARC ports and now it is officially removed in Java 15.
15.9. Foreign-Memory Access API (Second Incubator)
Java 14 JEP 370 introduced a new Foreign-Memory Access API as an Incubator Modules. This JEP made some changes to the APIs, and it will be still in incubator modules.
Example code:
import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemoryHandles; import jdk.incubator.foreign.MemorySegment; import java.lang.invoke.VarHandle; import java.nio.ByteOrder; public class HelloForeignMemory { public static void main(String[] args) { VarHandle intHandle = MemoryHandles.varHandle( int.class, ByteOrder.nativeOrder()); try (MemorySegment segment = MemorySegment.allocateNative(1024)) { MemoryAddress base = segment.baseAddress(); // print memory address System.out.println(base); // set value 999 into the foreign memory intHandle.set(base, 999); // get the value from foreign memory System.out.println(intHandle.get(base)); } } }
We need to add --add-modules jdk.incubator.foreign
to enable the incubator modules jdk.incubator.foreign
.:
$ javac --add-modules jdk.incubator.foreign HelloForeignMemory.java warning: using incubating module(s): jdk.incubator.foreign 1 warning $ java --add-modules jdk.incubator.foreign HelloForeignMemory WARNING: Using incubator modules: jdk.incubator.foreign MemoryAddress{ region: MemorySegment{ id=0x27c908f5 limit: 1024 } offset=0x0 } 999
15.10 Text Blocks
Finally, the multi-line string or text blocks is a permanent feature in Java 15.
History:
- Java 12 JEP 326 Raw String Literals
- Java 13 JEP 355: Text Blocks (Preview)
- Java 14 JEP 368: Text Blocks (Second Preview)
- Java 15, permanent feature.
Example code.:
String html = "<html>\n" + " <body>\n" + " <p>Hello, World</p>\n" + " </body>\n" + "</html>\n"; String java15 = """ <html> <body> <p>Hello, World</p> </body> </html> """;