Pattern Matching with instanceof
Pattern matching with instanceof
involves testing whether a
target's type matches the type in the pattern. If so, the target is converted to the type in
the pattern, and then the pattern variables are automatically initialized with data from the
target.
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 withinstanceof
and
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. See Safe Casting with instanceof and switch for more information.
This example uses two patterns:
Rectangle r
Circle c
The pattern Rectangle r
is an operand of the
instanceof
expression. It's testing if its target
s
has the type given in the pattern, which is
Rectangle
. Similarly, the expression s instanceof Circle
c
tests if s
has the type Circle
.
If a value successfully matches a pattern, then the pattern variables are
initialized with data from the target. In this example, if the target s
is a Rectangle
, then s
is converted to a
Rectangle
and then assigned to r
. Similarly, if
s
is a Circle
, then it's converted to a
Circle
and then assigned to c
,
Scope of Pattern Variables and instanceof
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 Variables and switch for more examples of where you can use a pattern variable.