JDK new features:修订间差异
标签:2017版源代码编辑 |
标签:2017版源代码编辑 |
||
第253行: | 第253行: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Difference between sealed/final: | Difference between sealed/final: |
2024年10月28日 (一) 06:16的版本
New Language Features
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)
Preview JDK 21
In Java 8, the compiler emitted a warning when '_' was used as an identifier. Java 9 took this a step further making the underscore character illegal as an identifier, reserving this name to have special semantics in the future. The goal of these actions was to prepare the stage for unnamed variables, which is a preview feature in Java 21.
var _ = mySet.add(x); // ignore the return value
try {
// ...
} catch (Exception _) { // ignore the exception object
// ...
}
list.stream()
.map((_) -> /* ... */) // ignore the parameter
.toList();
A related feature is called unnamed patterns, which can be used in pattern matching to ignore subpatterns.
if (r instanceof ColoredPoint(Point(int x, int y), _)) {
// ... x ... y ...
}
String Templates (Preview)
Preview JDK 21
String Templates are an extension to the single-line String literals and Text Blocks, allowing String interpolation and much more.
var name = "Duke";
var info = STR."My name is \{name}";
In addition to STR, there are a few more built-in processors. One is FMT, which is just like STR but accepts format specifiers that appear left to the embedded expression as specified by java.util.Formatter:
double value = 17.8D;
var result = FMT."Value is %7.2f\{value}";
Finally, there's the RAW processor which can be used to create StringTemplate objects, to be processed later by an other processor:
StringTemplate template = RAW."Hello \{name}";
// ...
String result = STR.process(template);
Unnamed Classes and Instance Main Methods (Preview)
Preview JDK 21
void main() {
System.out.println("Hello, World!");
}
Sealed Classes
Since JDK 17 Preview JDK 15 JDK 16
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]
Goals of sealed classes:
- Allow the author of a class or interface to control which code is responsible for implementing it.
- Provide a more declarative way than access modifiers to restrict the use of a superclass.
- Support future directions in pattern matching by providing a foundation for the exhaustive analysis of patterns.
Option1: define inner classes:
public abstract sealed class Chinese {
final class SimplifiedChinese extends Chinese{}
final class TradictionalChinese extends Chinese{}
}
// a sealed class could not be extended, otherwise you get an compiler error:
// public class Other extends Chinese {}
// 'Other' is not allowed in the sealed hierarchy
Option2:
public abstract sealed class Language permits English, Chinese{
}
// the permitted class is either final, sealed or non-sealed
public final class English extends Language{
}
Difference between sealed/final:
- both sealed/final class could not be extended again
- final class does not have sub classes
- sealed class contains sub classes
- if you want a sub class to be extended by the user, make it `non-sealed`
(The final modifier can be considered a special case of sealing, where extension/implementation is prohibited completely. That is, final is conceptually equivalent to sealed plus a permits clause which specifies nothing, though such a permits clause cannot be written.)