JDK new features

来自WHY42

JDK 22

JDK 21

Pattern Matching for switch

Since JDK 21 Preview JDK 20 JDK 19 JDK 18 JDK 17

Previously, switch was very limited: the cases could only test exact equality, and only for values of a few types: numbers, Enum types and Strings.

This feature enhances switch to work on any type and to match on more complex patterns.

These additions are backwards compatible, switch with the traditional constants work just as before, for example, with Enum values:

enum Status { Normal, Deleted }
public int getStatus(Status s) {
    return switch(s) {
        case Normal -> 0;
        case Deleted -> -1;
    };
}

However, now it also works with type patterns introduced by JEP 394: Pattern Matching for instanceof:

class Item {}
class Text extends Item {}
class Image extends Item {
    public String getImageType() { return "png"; }
}

public void printItem(Item item) {
    var msg = switch(item) {
        case Text ignored -> "Text";
        case Image ignored -> "Image";
        default -> "Unknow item";
    };
    System.out.println(msg);
}

A pattern supports guards, written as type pattern when guard expression:

var msg = switch(item) {
    case Text ignored -> "Text";
    case Image img when img.size > 1024 -> "Large Image";
    case Image ignored -> "Image";
    default -> "Unknow item";
};

Similarly to the type patterns in if conditions, the scope of the pattern variables are flow sensitive. Generally it works just as you'd expect, but there are many rules and edge cases involved.

Switch expressions always had to be exhaustive, in other words, they have to cover all possible input types. This is still the case with the new patterns, the Java compiler emits an error when the switch is incomplete.

Switch can now also match null values. Traditionally, when a null value was supplied to a switch, it threw a NullPointerException. For backwards compatibility, this is still the case when there's no explicit null pattern defined. However, now an explicit case for null can be added:

var msg = switch(item) {
    // without this, a NullPointerException throws if item is null.
    case null -> "Empty"; 
    case Text ignored -> "Text";
    case Image img when img.size > 1024 -> "Large Image";
    case Image ignored -> "Image";
    default -> "Unknow item";
};

This feature synergizes well with enums, Sealed Classes and generics. If there's only a fixed set of possible alternatives that exist, the default case can be omitted. Also, this feature helps a lot to maintain the integrity of the codebase when the domain is extended — for example, with a new constant in the enum. Due to the exhaustiveness check, all related switch expressions will yield a compiler error where the default case is not handled.

sealed interface I<T> permits A, B {}
final class A<X> implements I<String> {}
final class B<Y> implements I<Y> {}

static int testGenericSealedExhaustive(I<Integer> i) {
    return switch (i) {
        case B<Integer> bi -> 42;
    };
}

Exhaustiveness is checked at compile time but if at runtime a new implementation pops up (e.g. from a separate compilation), the compiler also inserts a synthetic default case that throws a MatchException.

The compiler also performs the opposite of the exhaustiveness check: it emits an error when a case completely dominates another.

    case Image ignored -> "Image";
    // Label is dominated by a preceding case label 'Image ignored'
    case Image img when img.size > 1024 -> "Large Image";

For readability reasons, the dominance checking forces constant case labels to appear before the corresponding type-based pattern.

Record Patterns

Since JDK 21 Preview JDK 20 JDK 19

Record Patterns extends the pattern matching capabilities of Java beyond simple type patterns to match and deconstruct Record values. It supports nesting to enable declarative, data focused programming.

interface Point { }
record Point2D(int x, int y) implements Point { }
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) { }

Object r = new ColoredPoint(new Point2D(3, 4), Color.GREEN);

Note, that while we use a Point2D to construct r, ColoredPoint accepts other Point implementations as well.

Without pattern matching, we'd need two explicit type checks to detect if object r is a ColoredPoint that holds Point2D, and also quite some work to extract the values x, y, and c:

if (r instanceof ColoredPoint) {
  ColoredPoint cp = (ColoredPoint) r;
  if (cp.p() instanceof Point2D) {
    Point2D pt = (Point2D) cp.p();
    int x = pt.x();
    int y = pt.y();
    Color c = cp.c();

    // work with x, y, and c
  }
}

Using type patterns for instanceof makes things a bit better as there's no longer a need for the casts, but conceptually the same thing needs to be done as previously:

if (r instanceof ColoredPoint cp && cp.p() instanceof Point2D pt) {
    int x = pt.x();
    int y = pt.y();
    Color c = cp.c();

    // work with x, y, and c
}

However this can be greatly simplified with Record Patterns that offer a concise way to test the shape of the data and to deconstruct it:

if (r instanceof ColoredPoint(Point2D(int x, int y), Color c)) {
  // work with x, y, and c
}

Patterns can be nested, which allows easy navigation in the objects, allowing the programmer to focus on the data expressed by those objects. Also it centralizes error handling. A nested pattern will only match if all subpatterns match. There's no need to manually handle each individual branch manually.

Record patterns also support not just instanceof but the Switch expression as well:

var length = switch (r) {
	case ColoredPoint(Point2D(int x, int y), Color c) -> Math.sqrt(x*x + y*y);
	case ColoredPoint(Point p, Color c) -> 0;
}

Unnamed Patterns and Variables (Preview)

String Templates (Preview)

JDK 17

JEP 409: Sealed Classes

Sealed Classes have been added to the Java Language. Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.

Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature. They were proposed again, with refinements, by JEP 397 and delivered in JDK 16 as a preview feature. Now in JDK 17, Sealed Classes are being finalized with no changes from JDK 16.

For further details, see JEP 409.[1]

JDK 11

[2] [3]