Java 17 new features explanation and code examples

Java 17 is the open source reference implementation of Java SE 17, officially released on September 14, 2021, and is another long-term support (LTS) version since Java 11. There are some new features and improvements in Java 17, and this article will give a brief introduction and examples of them.

Sealed

Sealed classes and interfaces restrict which other classes or interfaces can extend or implement them, enhancing encapsulation and maintainability. Sealed classes are covered by JEP 360 and delivered as a preview feature in JDK 15. They were proposed again, improved, by JEP 397 and made available as a preview feature in JDK 16. Now, in JDK 17, sealed classes are being finalized without any changes from JDK 16.

To define a sealed class or interface, use sealedmodifiers and specify in the declaration other classes or interfaces that are allowed to extend or implement it. These classes or interfaces are called subclasses or subinterfaces. Subclasses or subinterfaces can use finala modifier to indicate that they cannot be further extended or implemented, or a sealedmodifier to indicate that they are also sealed and need to specify their subclass or subinterface, or a non-sealedmodifier to indicate that they are not sealed and can be extended or implemented by any class or interface.

For example, we can define a sealed shape interface Shape and specify that it can only be implemented by three classes: Circle, Rectangle, and Triangle:

public sealed interface Shape permits Circle, Rectangle, Triangle {
    double area();
}

Then, we can define these three classes and use the final, sealedand non-sealedmodifiers respectively:

public final class Circle implements Shape {
    private final double radius;

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

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

public sealed class Rectangle implements Shape permits Square {
    private final double length;
    private final double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double area() {
        return length * width;
    }
}

public non-sealed class Triangle implements Shape {
    private final double base;
    private final double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    public double area() {
        return base * height / 2;
    }
}

Note that Square is a subclass of Rectangle and is also sealed:

public final class Square extends Rectangle {
    public Square(double side) {
        super(side, side);
    }
}

In this way, we can guarantee that the Shape interface can only be implemented by these four classes, and there will be no other possibilities. We can also use pattern matching to operate on different shapes, for example:

public static void printArea(Shape shape) {
    if (shape instanceof Circle c) {
        System.out.println("The area of the circle is " + c.area());
    } else if (shape instanceof Rectangle r) {
        System.out.println("The area of the rectangle is " + r.area());
    } else if (shape instanceof Triangle t) {
        System.out.println("The area of the triangle is " + t.area());
    }
}

switch expression

The switch expression allows the switch to have a return value, and can be directly assigned to a variable as the result, which simplifies the logic of multi-branching. switch expressions are covered by JEP 325 and delivered as a preview feature in JDK 12. They were proposed again, improved, by JEP 354 and made available as a preview feature in JDK 13. They were proposed again, improved, by JEP 361 and made available as a preview feature in JDK 14. Now, in JDK 17, switch expressions are being finalized without any changes from JDK 14.

To define a switch expression, you need to use ->symbols to represent the results of each branch, and add a semicolon at the end of the expression. For example, we can define a switch expression that returns seasons based on months:

public static String getSeason(int month) {
    return switch (month) {
        case 12, 1, 2 -> "Winter";
        case 3, 4, 5 -> "Spring";
        case 6, 7, 8 -> "Summer";
        case 9, 10, 11 -> "Autumn";
        default -> "Unknown";
    };
}

Note that we can use commas to separate multiple matching values, or use default to indicate other situations. We can also use the yield keyword to return a value, which is useful when we need to perform some operations before returning, for example:

public static String getSeason(int month) {
    return switch (month) {
        case 12, 1, 2 -> {
            System.out.println("It's cold!");
            yield "Winter";
        }
        case 3, 4, 5 -> {
            System.out.println("It's warm!");
            yield "Spring";
        }
        case 6, 7, 8 -> {
            System.out.println("It's hot!");
            yield "Summer";
        }
        case 9, 10, 11 -> {
            System.out.println("It's cool!");
            yield "Autumn";
        }
        default -> {
            System.out.println("It's unknown!");
            yield "Unknown";
        }
    };
}

text block

Text blocks allow the use of three double quotes to define a multi-line string, avoiding the trouble of escaping and splicing. Text Blocks by JEP

355 and delivered as a preview feature in JDK 13. They were proposed again, improved, by JEP 368 and made available as a preview feature in JDK 14. They were proposed again, improved, by JEP 378 and made available as a preview feature in JDK 15. Now, in JDK 17, text blocks are being finalized without any changes from JDK 15.

To define a text block, you need to use three double quotes """to start and end, and the end three double quotes cannot be on the same line as the start. For example, we can define a text block containing JSON data:

public static String getJson() {
    return """
    {
        "name": "Java",
        "version": 17,
        "features": [
            "sealed classes",
            "switch expressions",
            "text blocks"
        ]
    }
    """;
}

Note that line breaks in text blocks are preserved and do not need to be denoted by \n. We can also use \to indicate that newlines are ignored, or to \sindicate a space. For example:

public static String getGreeting() {
    return """
    Hello,\
    World!
    """;
}

public static String getPoem() {
    return """
    Twinkle, twinkle, little star,\s
    How I wonder what you are.\s
    Up above the world so high,\s
    Like a diamond in the sky.
    """;
}

pattern matching

Pattern matching allows the use of patterns in instanceof and switch to test the type and structure of expressions, improving the readability and flexibility of the code. Pattern matching is covered by JEP 305 and delivered as a preview feature in JDK 14. They were proposed again, improved, by JEP 375 and made available as a preview feature in JDK 15. They were proposed again, improved, by JEP 394 and made available as a preview feature in JDK 16. Now, in JDK 17, pattern matching is being finalized without any changes from JDK 16.

To use pattern matching, you need to use instanceofthe or switchkeyword, and add a variable name after the type of test to bind the matching value. For example, we can use pattern matching to determine whether an object is a string and get its length:

public static void printLength(Object obj) {
    if (obj instanceof String s) {
        System.out.println("The length of the string is " + s.length());
    } else {
        System.out.println("The object is not a string");
    }
}

Note that we don't need to cast anymore, because the variable sis already bound as a string. We can also use pattern matching to determine whether an object is a subclass of a sealed class and get its properties:

public static void printShape(Shape shape) {
    switch (shape) {
        case Circle c -> System.out.println("The radius of the circle is " + c.radius());
        case Rectangle r -> System.out.println("The area of the rectangle is " + r.area());
        case Triangle t -> System.out.println("The base of the triangle is " + t.base());
        default -> System.out.println("Unknown shape");
    }
}

Note that we don't need to do type checking or casting, because the variables c, rand tare already bound to the corresponding types.

Enhanced Pseudorandom Number Generator

The Enhanced Pseudorandom Number Generator provides new interface types and implementations for PRNGs, including jumpable PRNGs and an additional class of splittable PRNG algorithms (LXM). The Enhanced Pseudo-Random Number Generator is provided by JEP 356 and as an official feature in JDK 17.

To use the enhanced pseudo-random number generator, use the java.util.randomnew interfaces and classes in the package. For example, we can use RandomGeneratorthe interface to get an instance of a PRNG and use it to generate various types of random numbers:

public static void generateRandomNumbers() {
    RandomGenerator random = RandomGenerator.getDefault();
    System.out.println("A random boolean: " + random.nextBoolean());
    System.out.println("A random int: " + random.nextInt());
    System.out.println("A random long: " + random.nextLong());
    System.out.println("A random float: " + random.nextFloat());
    System.out.println("A random double: " + random.nextDouble());
}

Note that RandomGeneratorthe interface provides many convenient methods to generate random numbers in different ranges and distributions, such as nextInt(int bound), nextLong(long bound), and nextGaussian()so on. We can also use RandomGeneratorFactoryclasses to get instances of different PRNG algorithms, for example:

public static void useDifferentAlgorithms() {
    RandomGenerator random1 = RandomGeneratorFactory.of("L32X64MixRandom");
    RandomGenerator random2 = RandomGeneratorFactory.of("L64X128MixRandom");
    RandomGenerator random3 = RandomGeneratorFactory.of("L128X256MixRandom");
    System.out.println("Using L32X64MixRandom: " + random1.nextInt());
    System.out.println("Using L64X128MixRandom: " + random2.nextInt());
    System.out.println("Using L128X256MixRandom: " + random3.nextInt());
}

Note that RandomGeneratorFactorythe class provides many methods to obtain or query different PRNG algorithms, such as of(String name), all(), preferred()and so on. We can also use JumpableRandomGeneratorinterfaces or SplittableRandomGeneratorinterfaces to obtain a jumpable or splittable PRNG instance, and use them to generate different sub-generators, for example:

public static void useJumpableOrSplittableGenerators() {
    JumpableRandomGenerator jumpable = RandomGeneratorFactory.jumpable();
    SplittableRandomGenerator splittable = RandomGeneratorFactory.splittable();
    System.out.println("Using the original jumpable generator: " + jumpable.nextInt());
    System.out.println("Using the original splittable generator: " + splittable.nextInt());
    JumpableRandomGenerator jumped = jumpable.jump();
    SplittableRandomGenerator splitted = splittable.split();
    System.out.println("Using the jumped generator: " + jumped.nextInt());
    System.out.println("Using the splitted generator: " + splitted.nextInt());
}

Note that both JumpableRandomGeneratorinterfaces and SplittableRandomGeneratorinterfaces inherit from RandomGeneratorinterfaces, and provide additional methods to generate sub-generators, such as jump(), split()etc. These sub-generators can be used for parallel computing or other scenarios.

New macOS rendering pipeline

The new macOS rendering pipeline uses the Apple Metal accelerated rendering API to replace the OpenGL API, which was deprecated by Apple. The new macOS rendering pipeline is provided by JEP 382 and is an official feature in JDK 17.

To use the new macOS rendering pipeline, a system property needs to be set when running a Java program:

-Dsun.java2d.metal=true

This way, the Java 2D API used by the Java 2D API and the Swing API for rendering can use the Metal API to speed up rendering. This is transparent to Java programs as this is an internal implementation difference and has no effect on the Java API. Metal pipelines require macOS 10.14.x or later. Attempts to set this on earlier versions will be ignored.

Guess you like

Origin blog.csdn.net/caicai250/article/details/131415249