Core Java

New Features in Java 15

Java 15, released in September 2020, introduced several features and enhancements. Let us delve into Java 15 new features.

1. Java 15 Records (JEP 384)

Java 14 introduced a preview feature known as the record class type, designed to simplify the creation of immutable data objects. In Java 15, this record type has been further enhanced, although it remains in preview status.

  • Records automatically provide an implicit constructor that includes all parameters as field variables. Additionally, they offer implicit getter and setter methods for each field variable, streamlining the process of working with data objects.
  • The implicit implementation of hashCode(), equals(), and toString() methods make record objects inherently convenient for handling common operations.
  • Notably, with Java 15, the declaration of native methods within records is no longer allowed. Moreover, the implicit fields of a record are not marked as final. Attempts to modify them using reflection will result in an IllegalAccessException.

2. Java 15 Sealed Classes (JEP 360)

Java 15 introduces sealed classes as a preview feature, offering fine-grained control over inheritance. The following key points should be considered when working with sealed classes:

  • Sealed class declaration: A sealed class is declared using the sealed keyword.
  • Permitted subtypes: Sealed classes allow the declaration of permitted subtypes using the permits keyword.
  • Extending a sealed class: A class extending a sealed class must be declared as either sealed, non-sealed, or final.
  • Hierarchy control: Sealed classes assist in creating a finite and determinable hierarchy of classes in inheritance.

Below is an example of Java code illustrating the use of sealed classes introduced in Java 15 (JEP 360):

// Sealed class (Parent class)
sealed class Shape permits Circle, Square {
    // Common properties or methods for all subclasses
    public abstract double area();
}

// Subclass 1: Circle (Permitted subtype)
final class Circle extends Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// Subclass 2: Square (Permitted subtype)
non-sealed class Square extends Shape {
    private final double side;

    public Square(double side) {
        this.side = side;
    }

    @Override
    public double area() {
        return side * side;
    }
}

// Subclass 3: Triangle (Not permitted, compilation error)
// Error: Triangle is not allowed to extend Shape
// class Triangle extends Shape {
//     // implementation...
// }

In this example:

  • Shape is a sealed class: It explicitly permits two subclasses: Circle and Square.
  • Circle and Square: These are the permitted subclasses and provide specific implementations for the area() method.
  • Square is declared as non-sealed: This means it allows further subclasses beyond the ones explicitly permitted.
  • Attempting to create a new subclass: If you attempt to create a new subclass outside of the permitted list (e.g., a Triangle class), it would result in a compilation error.

Keep in mind that sealed classes are designed to provide a controlled hierarchy, and the permits clause specifies which classes are allowed to extend the sealed class. This feature helps in creating more maintainable and predictable class hierarchies.

3. Java 15 Hidden Classes (JEP 371)

Java 15 introduces Hidden Classes as part of JEP 371. Hidden Classes provide a mechanism for defining classes that are not discoverable by the class loader. This feature is particularly aimed at addressing the needs of frameworks and libraries that generate classes dynamically at runtime.

  • Definition: Hidden Classes are classes that cannot be directly discovered by the class loader.
  • Motivation: The primary motivation is to support frameworks and libraries that generate classes dynamically at runtime without affecting the application’s classpath.
  • Use Cases: Hidden Classes are useful for advanced use cases where dynamically generated classes need to be isolated and kept separate from the main application classpath.
  • Security and Encapsulation: They enhance security and encapsulation by preventing direct access to dynamically generated classes from outside the framework or library.
  • Accessor Class: Accessor classes are used to interact with hidden classes, allowing controlled access to their members.

Creating an example of Java 15 Hidden Classes (JEP 371) involves more advanced and specific use cases, as Hidden Classes are typically used in scenarios where classes need to be dynamically generated at runtime for specific frameworks or libraries. Below is a simplified example to illustrate the basic concepts:

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class HiddenClassesExample {

    public static void main(String[] args) {
        try {
            // Create a lookup object for the current class
            MethodHandles.Lookup lookup = MethodHandles.lookup();

            // Define a method type for the hidden class
            MethodType methodType = MethodType.methodType(String.class);

            // Create a hidden class with a specific name and method type
            Class hiddenClass = MethodHandles
                    .privateLookupIn(HiddenClassesExample.class, lookup)
                    .defineHiddenClass("HiddenDynamicClass", true, MethodHandles.Lookup.ClassOption.NESTMATE,
                            methodType)
                    .lookupClass();

            // Instantiate the hidden class
            Object instance = hiddenClass.getDeclaredConstructor().newInstance();

            // Access a method from the hidden class using MethodHandles
            String result = (String) lookup.findVirtual(hiddenClass, "hiddenMethod", methodType)
                    .invoke(instance);

            System.out.println("Result from hidden method: " + result);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private static class HiddenDynamicClass {
        public String hiddenMethod() {
            return "Hello from the hidden method!";
        }
    }
}

In this example:

  • Use of MethodHandles.Lookup: We use MethodHandles.Lookup to create a lookup object for the current class.
  • Definition of MethodType: A MethodType is defined for the hidden class method.
  • Usage of MethodHandles.privateLookupIn: MethodHandles.privateLookupIn is used to create a hidden class named “HiddenDynamicClass” with a specific method type.
  • Instantiation and Method Invocation: An instance of the hidden class is instantiated, and its method is invoked using MethodHandles.

4. Java 15 Pattern Matching Type Checks (JEP 375)

Java 15 introduces Pattern Matching Type Checks as part of JEP 375. This enhancement simplifies and enhances the code by providing a more concise and expressive way to perform type checks in conjunction with pattern matching.

  • Enhanced Type Checking: Pattern Matching Type Checks allow for more readable and concise code when checking the type of an object.
  • Combining Type Check and Cast: With the new syntax, the type check and cast operation are combined into a single statement, reducing boilerplate code.
  • Use with instanceof: The feature integrates seamlessly with the existing instanceof operator, making it a natural extension of type-checking capabilities.
  • Improved Code Readability: By combining type checks with variable declarations, the code becomes more concise and easier to understand, especially in scenarios involving complex conditional checks.

Consider the following Java code snippet that demonstrates the usage of Pattern Matching Type Checks:

public class Example {
  public static void main(String[] args) {
    Object obj = "Hello, Pattern Matching!";

    // Old approach
    if (obj instanceof String) {
      String str = (String) obj;
      System.out.println("Length of the string: " + str.length());
    } else {
      System.out.println("Not a string!");
    }

    // New approach with Pattern Matching Type Checks
    if (obj instanceof String str) {
      System.out.println("Length of the string: " + str.length());
    } else {
      System.out.println("Not a string!");
    }
  }
}

In the example above, Pattern Matching Type Checks allow for a more concise and readable way to perform type checks and casts, reducing the need for explicit casting and boilerplate code.

This enhancement significantly improves code readability and is particularly beneficial in scenarios where type-checking is a common task.

5. Java 15 Foreign Memory API (JEP 383)

Java 15 introduces the Foreign Memory API as part of JEP 383. This API provides a set of tools for interacting with native memory, allowing Java programs to work more efficiently and seamlessly with native libraries and data.

  • Direct Access to Native Memory: The Foreign Memory API enables direct and efficient access to native memory outside the Java heap.
  • Memory Segments: The API introduces the concept of memory segments, which are contiguous regions of native memory. Developers can create, manipulate, and access data within these segments.
  • Memory Accessors: The API provides memory accessors for reading and writing data of different types within memory segments.
  • Interoperability with Native Code: The Foreign Memory API facilitates better interoperability with native libraries and code by allowing direct memory access.
  • Safety and Control: While providing direct memory access, the API includes features for managing safety and control to prevent common issues like buffer overflows.

Let’s explore a simple example to demonstrate the usage of the Foreign Memory API in Java 15.

public class ForeignMemoryExample {

  public static void main(String[] args) {
    // Create a memory segment of size 10 bytes
    MemorySegment segment = MemorySegment.allocateNative(10);

    // Accessor for int values within the memory segment
    MemoryAccessSegment  intAccessor = segment.accessSegment().asSegment(0, Integer.BYTES);

    // Write values to the memory segment
    intAccessor.set(0, 42);
    intAccessor.set(1, 99);

    // Read values from the memory segment
    int value1 = intAccessor.get(0);
    int value2 = intAccessor.get(1);

    // Print the values
    System.out.println("Value 1: " + value1);
    System.out.println("Value 2: " + value2);
  }
}

In this example, we allocate a native memory segment of size 10 bytes, create an accessor for int values within the segment, and perform read and write operations. The Foreign Memory API simplifies the interaction with native memory in a controlled and efficient manner.

The Foreign Memory API in Java 15 provides a powerful set of tools for dealing with native memory, enhancing the capabilities of Java applications in scenarios requiring efficient memory management.

6. Java 15 Garbage Collectors

Java 15 comes with various Garbage Collectors that play a crucial role in managing memory and ensuring efficient resource utilization. Let’s explore the different garbage collectors introduced in this version:

  • G1 (Garbage First) Collector: G1 is a server-style garbage collector that is designed for multi-processor machines with large memories. It provides high throughput and low-latency garbage collection by dividing the heap into regions and using a compacting collector.
  • Parallel Collector: The Parallel Collector, also known as the throughput collector, is designed for applications with medium to large datasets that prioritize throughput. It achieves high throughput by utilizing multiple threads for garbage collection.
  • Z Garbage Collector (ZGC): ZGC is a low-latency garbage collector designed for applications that require consistent response times and scalability. It minimizes pause times by using a region-based approach and concurrent phases.
  • Shenandoah Garbage Collector: Shenandoah is another low-latency garbage collector that aims to reduce pause times for large heaps. It employs a region-based algorithm and performs garbage collection concurrently with the application threads.
  • Epsilon Garbage Collector: Epsilon is a no-op garbage collector introduced for performance testing and memory-only configurations. It does not perform any garbage collection, allowing developers to assess the performance impact of garbage collection overhead.

7. Java 15 Other Changes

Java 15 introduces various other changes and enhancements beyond the features discussed earlier. While the focus has been on major enhancements like Sealed Classes, Pattern Matching, Foreign Memory API, and Garbage Collectors, there are additional improvements that contribute to the overall development experience. Below are some notable changes:

  • Text Block Refinements: Java 15 introduces refinements to text blocks, enhancing the legibility and maintainability of multiline string literals.
  • Performance Optimizations: Each release of Java focuses on continuous efforts to optimize the performance of the Java Virtual Machine (JVM) and various core libraries. Java 15 incorporates specific performance optimizations and enhancements.
  • API Improvements: Java 15 introduces additional methods, classes, or enhancements to existing APIs, offering developers a broader set of tools and options for application development.
  • Security Enhancements: Regular Java updates include not only feature additions but also security patches and improvements to fortify the platform’s security. It’s crucial to stay informed about the latest security features and adhere to best practices.
  • Tooling Advancements: As part of the Java release cycle, updates to development tools, compilers, and other utilities are integrated. These updates aim to boost developer productivity and ensure better compatibility with contemporary development environments.

8. Conclusion

In Java 15, several significant language enhancements and features were introduced, each contributing to the ongoing evolution and refinement of the Java programming language. The introduction of Records (JEP 384) marked a notable stride toward simplifying the creation of immutable data objects by automatically generating common methods, streamlining code, and enhancing readability. Sealed Classes (JEP 360) brought fine-grained control over class hierarchies, allowing developers to explicitly define and limit subclasses, fostering improved design patterns. Hidden Classes (JEP 371) introduced a dynamic and lightweight mechanism for generating classes at runtime, catering to specific frameworks and libraries. Pattern Matching Type Checks (JEP 375) further enhanced the expressiveness of Java, making type-checking more concise and readable. The introduction of the Foreign Memory API (JEP 383) provided tools for efficient interaction with native memory, expanding the capabilities of Java applications. Garbage Collectors received attention with different options like G1, Parallel, ZGC, and Shenandoah, each catering to specific performance needs. Finally, miscellaneous changes encompassed improvements in text blocks, performance optimizations, API enhancements, security updates, and tooling improvements, collectively refining the overall Java development experience. These additions collectively demonstrate Java’s commitment to adapting to modern programming challenges and providing developers with a versatile and powerful platform.

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button