Record Patterns
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.
For background information about record patterns, see JEP 440.
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
pattern list. In this example, the type is Point
and the 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 Record Patterns
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);
}
}
Using var in Record Patterns
You can use var in the record pattern's component list. The compiler can
infer the type of the type arguments for record patterns in all constructs that accept
patterns: switch
statements, switch
expressions, and
instanceof
expressions.
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 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);
}
}
In the following example, the compiler infers MyPair(var s, var
i)
as MyPair<String, Integer>(String s, Integer
i)
:
record MyPair<T, U>(T x, U y) { }
// ...
static void recordInference(MyPair<String, Integer> p) {
switch (p) {
case MyPair(var s, var i) ->
System.out.println(s + ", #" + i);
}
}
Primitive Types in Record Patterns
You can perform pattern matching on the types of a record's components, even if these types are primitive.
Note:
Support for pattern matching on primitive types of a record's components 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.For more information, see JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview).
In the following example, the record Cup
contains one
component of type double
. The example creates a Cup
that contains a float
and then tests whether the Cup
contains a double
, float
, or int
value with the instanceof
operator:
record Cup(double d) { }
//...
Cup myCup = new Cup(34.567f);
if (myCup instanceof Cup(double d)) System.out.println("Cup contains double: " + d);
if (myCup instanceof Cup(float f)) System.out.println("Cup contains float: " + f);
if (myCup instanceof Cup(int i)) System.out.println("Cup contains int: " + i);
It prints output similar to the following:
Cup contains double: 34.56700134277344
Cup contains float: 34.567
Similarly, as described in Type Patterns with Primitive Types, a value of a record's component matches the corresponding type pattern if it's
safe to cast the value to the type in the type pattern. A cast is safe if
there's no loss of information or exception thrown during the cast or conversion. For
example, casting a float
to a double
or
float
is safe; casting a float
to an
int
isn't. (See the section Decimal ↔ Binary Conversion Issues
in the Double JavaDoc API documentation for an
explanation why the converted double
value is different than the
original float
value in this example.)
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);
}
}