Pattern Matching for the instanceof Operator
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 s) throws IllegalArgumentException {
if (s instanceof Rectangle) {
Rectangle r = (Rectangle) s;
return 2 * r.length() + 2 * r.width();
} else if (s instanceof Circle) {
Circle c = (Circle) s;
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 (s instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (s 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 test, which is called a
predicate; a target; and a set of local variables, which are called
pattern variables. The getPerimeter
example contains two
patterns, s instanceof Rectangle r
and s instanceof Circle
c
:
- The predicate is a Boolean-valued function with one argument. In these
two patterns, it’s the
instanceof
operator, testing if thes
argument is aRectangle
or aCircle
. - The target is the argument of the predicate. In these two patterns,
it's the
s
value. - The pattern variables are those that store data from the target only
if the predicate returns
true
. In these two patterns, they're the variablesr
andc
.
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
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.
See Scope of Pattern Variable Declarations for more examples of where you can use a pattern variable.