Generally, pattern matching is referred to as testing some data to see if it has a particular structure and verifying it is a match or not, as we do in regular expressions. The following JEPs enhanced this Java feature to be used with instanceof operator to make it more concise and robust.
The pattern matching for instanceof operator avoids the boilerplate code to type test and cast to a variable more concisely. Let us understand pattern matching in-depth with a few examples.
1. Traditional Approach
Many times in an application, we need to check if an object is of type A or B because how we handle each type is different.
For example, a Customer
can be of type BusinessCustomer
or PersonalCustomer
. Based on the type of customer, we may fetch the information based on context. There are three things going on here for each type:
- a test (customer instanceof PersonalCustomer)
- a conversion (casting
customer
to PersonalCustomer or BusinessCustomer) - a declaration of a new local variable (pc or bc)
Notice that the casting step seems unnecessary because we have already tested for the instance in the if statement. The pattern-matching enhancement tries to avoid this boilerplate code, as shown in the next section.
Customer customer = null;
String customerName;
if(customer instanceof PersonalCustomer)
{
PersonalCustomer pc = (PersonalCustomer) customer; //Redundant casting
customerName = String.join(" ", pc.getFirstName(), pc.getMiddleName(), pc.getLastName());
}
else if(customer instanceof BusinessCustomer)
{
BusinessCustomer bc = (BusinessCustomer) customer; //Redundant casting
customerName = bc.getLegalName();
}
2. Pattern Matching
Now, with pattern matching for instanceof, we can write a similar code in the below manner. Here we can reduce the boilerplate code of typecasting (i.e. casting
to customer
pc
).
In the following example, the customer variable is tested with the type PersonalCustomer; if the Predicate is true, it is assigned to the variable pc
. Similar pattern checking is done for the BusinessCustomer type.
Customer customer = null;
String customerName;
if(customer instanceof PersonalCustomer pc)
{
customerName = String.join(" ", pc.getFirstName(), pc.getMiddleName(), pc.getLastName());
}
else if(customer instanceof BusinessCustomer bc)
{
customerName = bc.getLegalName();
}
So essentially, a type test pattern (used in instanceof
) consists of a predicate specifying a type and a single binding variable. we can deduce from the above definition that a pattern is a combination of :
- a predicate that can be applied to a target, and
- a set of binding variables that are extracted from the target only if the predicate successfully applies to it
In the code below, the phrase String s
is the type test pattern:
if (obj instanceof String s) {
// can use 's' here
}
Note that the pattern will only match, and
s
will only be assigned, if obj is notnull
.
3. Scope of Variables
we need to be a little careful about the scope of such pattern variables. Note that the assignment of pattern variable happens only when the predicate test is true. This affects the scope of the variable as well.
In the following example, as the print statement will execute only when the predicate fails, so this statement is not even compiled.
if (!(obj instanceof String s)) {
//System.out.println(s); //compiler error - cannot access 's' here
}
The variable is available in the enclosing if block, and we cannot access it outside it.
Object obj = new Object();
if (obj instanceof String s) {
System.out.println(s); //can use 's' here
} else if (obj instanceof Number n) {
//System.out.println(s); //can not use 's' here
}
//System.out.println(s); //can not use 's' here
If we are writing complex conditional statements in the if-statement then we can use the pattern variable only with &&
(AND) operator because the variable will be accessed only when the predicate has been tested to be true. We cannot use the variable with ||
(OR) operator because the predicate may be tested false in a few cases, and then we will be exceptions of different kinds.
//Allowed to use s in complex condition
if (obj instanceof String s && s.startsWith("a")) {
System.out.println(s);
}
//compiler error
if (obj instanceof String s || s.startsWith("a")) {
System.out.println(s);
}
4. Conclusion
In this Java tutorial, we explored the traditional instanceof operator and the enhancements introduced with the pattern matching for instanceof. We learned how the scope of the pattern variable is affected by the result of the test predicate, with a few examples.
Happy Learning !!
Leave a Reply