4 Pattern Matching for instanceof
Pattern matching involves testing whether an object has a particular structure, then extracting data from that object if there's a match. You can already do this with Java; however, pattern matching introduces new language enhancements that enable you to conditionally extract data from objects with code that's more concise and robust.
For background information about pattern matching for the
instanceof
operator, see JEP 394.
Consider the following code that calculates the perimeter of certain shapes:
public interface Shape {
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return 2 * r.length() + 2 * r.width();
} else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
}
public class Rectangle implements Shape {
final double length;
final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return length; }
double width() { return width; }
}
public class Circle implements Shape {
final double radius;
public Circle(double radius) {
this.radius = radius;
}
double radius() { return radius; }
}
The method getPerimeter
performs the following:
- A test to determine the type of the
Shape
object - A conversion, casting the
Shape
object toRectangle
orCircle
, depending on the result of theinstanceof
operator - A destructuring, extracting either the length and width or the radius from the
Shape
object
Pattern matching enables you to remove the conversion step by changing the
second operand of the instanceof
operator with a type pattern, making
your code shorter and easier to read:
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (shape instanceof Circle c) {
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
Note:
Removing this conversion step also makes your code safer. Testing an object's type with theinstanceof
, then assigning that object to a new variable with a
cast can introduce coding errors in your application. You might change the type of one
of the objects (either the tested object or the new variable) and accidentally forget
to change the type of the other object.
A pattern is a combination of a predicate, or test, that can be
applied to a target and a set of local variables, called pattern variables, that
are assigned values extracted from the target only if the test is successful. The
predicate is a Boolean-valued function of one argument; in this case, it’s
the instanceof
operator testing whether the Shape
argument is a Rectangle
or a Circle
. The target
is the argument of the predicate, which is the Shape
value. The pattern
variables are those that store data from the target only if the predicate returns
true
, which are the variables r
and
s
.
A type pattern consists of a predicate that specifies a type, along
with a single pattern variable. In this example, the type patterns are Rectangle
r
and Circle c
.
Scope of Pattern Variables
The scope of a pattern variable are the places where the program can
reach only if the instanceof
operator is true
:
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle s) {
// You can use the pattern variable s (of type Rectangle) here.
} else if (shape instanceof Circle s) {
// You can use the pattern variable s of type Circle here
// but not the pattern variable s of type Rectangle.
} else {
// You cannot use either pattern variable here.
}
}
The scope of a pattern variable can extend beyond the statement that introduced it:
public static boolean bigEnoughRect(Shape s) {
if (!(s instanceof Rectangle r)) {
// You cannot use the pattern variable r here because
// the predicate s instanceof Rectangle is false.
return false;
}
// You can use r here.
return r.length() > 5;
}
You can use a pattern variable in the expression of an
if
statement:
if (shape instanceof Rectangle r && r.length() > 5) {
// ...
}
Because the conditional-AND operator (&&
) is
short-circuiting, the program can reach the r.length() > 5
expression only if the instanceof
operator is
true
.
Conversely, you can't pattern match with the instanceof
operator in
this situation:
if (shape instanceof Rectangle r || r.length() > 0) { // error
// ...
}
The program can reach the r.length() || 5
if the
instanceof
is false; thus, you cannot use the pattern variable
r
here.