Record Patterns
Note:
This is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.For background information abot record patterns, see JEP 432.
You can use a record pattern to test whether a value is an instance of a
record class type (see Record Classes) and, if it is, to recursively perform pattern matching on its
component values. The following example tests whether obj
is an
instance of the Point
record with the record pattern
Point(double x, double y)
:
record Point(double x, double y) {}
static void printAngleFromXAxis(Object obj) {
if (obj instanceof Point(double x, double y)) {
System.out.println(Math.toDegrees(Math.atan2(y, x)));
}
}
In addition, this example extracts the x
and
y
values from obj
directly, automatically calling
the Point
record's accessor methods.
A record pattern consists of a type and a (possibly empty) record
component pattern list. In this example, the type is Point
and the
component pattern list is (double x, double y)
.
Note:
Thenull
value does
not match any record pattern.
The following example is the same as the previous one except it uses a type pattern instead of a record pattern:
static void printAngleFromXAxisTypePattern(Object obj) {
if (obj instanceof Point p) {
System.out.println(Math.toDegrees(Math.atan2(p.y(), p.x())));
}
}
Generic Records
If a record class is generic, then you can explicitly specify the type arguments in a record pattern. For example:
record Box<T>(T t) { }
static void printBoxContents(Box<String> bo) {
if (bo instanceof Box<String>(String s)) {
System.out.println("Box contains: " + s);
}
}
You can test whether a value is an instance of a parameterized record type provided that the value could be cast to the record type in the pattern without requiring an unchecked conversion. The following example doesn't compile:
static void uncheckedConversion(Box bo) {
// error: Box cannot be safely cast to Box<String>
if (bo instanceof Box<String>(var s)) {
System.out.println("String " + s);
}
}
Type Inference
You can use var
in the record pattern's component list.
In the following example, the compiler infers that the pattern variables
x
and y
are of type
double
:
static void printAngleFromXAxis(Object obj) {
if (obj instanceof Point(var x, var y)) {
System.out.println(Math.toDegrees(Math.atan2(y, x)));
}
}
The compiler can infer the type of the type arguments for record patterns
in all constructs that accept patterns: switch
statements and
expressions, instanceof
expressions, and enhanced
for
statements.
The following example is equivalent to
printBoxContents
. The compiler infers its type argument and pattern
variable: Box(var s)
is inferred as
Box<String>(String s)
static void printBoxContentsAgain(Box<String> bo) {
if (bo instanceof Box(var s)) {
System.out.println("Box contains: " + s);
}
}
Nested Record Patterns
You can nest a record pattern inside another record pattern:
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record ColoredRectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
static void printXCoordOfUpperLeftPointWithPatterns(ColoredRectangle r) {
if (r instanceof ColoredRectangle(
ColoredPoint(Point(var x, var y), var upperLeftColor),
var lowerRightCorner)) {
System.out.println("Upper-left corner: " + x);
}
}
You can do the same for parameterized records. The compiler infers the
types of the record pattern's type arguments and pattern variables. In the following
example, the compiler infers Box(Box(var s))
as
Box<Box<String>>(Box(String s))
.
static void nestedBox(Box<Box<String>> bo) {
// Box(Box(var s)) is inferred to be Box<Box<String>>(Box(var s))
if (bo instanceof Box(Box(var s))) {
System.out.println("String " + s);
}
}
Enhanced for Statements
A record pattern can appear in an enhanced for
statement:
record Pair<T>(T x, T y) {}
static void printPairArray(Pair[] pa) {
for (Pair(var first, var second) : pa) {
System.out.println("(" + first + ", " + second + ")");
}
}
If an enhanced for
statement that contains a record
pattern encounters a null
value, then it throws a
MatchException
. The following example throws a
MatchException
when the enhanced for
statement
in printPairArray
encounters the null
value in
mypa
:
Pair[] mypa = new Pair[]{
new Pair<Integer>(1,2),
null,
new Pair<String>("hello","world")
};
printPairArray(mypa);