Java 16 finalized Pattern Matching for instanceof (JEP 394).
What is pattern matching?
The process of testing a value against a pattern is known as pattern matching. If a value successfully matches a pattern, then the process of pattern matching initializes the pattern variables, if any, declared by the pattern.
Pattern matching for instnaceof
The instanceof operator checks if an object matches a type. If it does, the object is automatically cast to that type and assigned to a new variable.
That means, we can now write this:
if (obj instanceof String s) {
}
instead of:
if (obj instanceof String) {
String s = (String) obj;
}
Quick walk through
In following code if obj is an instance of String, then it is cast to String and assigned to the binding variable s.
public class InstanceOfPatternMatching {
public static void main(String... args) {
Object str = "Hello, World!";
if (str instanceof String s) {
System.out.println("The length of the string is: " + s.length());
}
}
}
Output$ javac InstanceOfPatternMatching.java $ java InstanceOfPatternMatching The length of the string is: 13
JDK 16
Flow Scoping
Flow scoping is the compiler’s ability to introduce and track the pattern variable’s scope based on the program’s execution flow.
public class FlowScopeExample1 {
public static void main(String[] args) {
Object obj = "my string";
if (obj instanceof String s) {
System.out.println("string length in if part: " + s.length());
} else {
//s is not available here, compile error if uncommented next line
//System.out.println("string length in else part: "+s.length());
}
}
}
Output$ javac FlowScopeExample1.java $ java FlowScopeExample1 string length in if part: 9
JDK 16
Flow Scoping, negated conditions
If the instanceof check is negated, the pattern variable is in scope in the else block where the condition effectively becomes true.
public class FlowScopeExample2 {
public static void main(String[] args) {
Object obj = "my string";
if (!(obj instanceof String s)) {
//s is not available here, compile error if uncommented next line
//System.out.println("string length in if part: "+s.length());
} else {
System.out.println("string length in else part: " + s.length());
}
}
}
Output$ javac FlowScopeExample2.java $ java FlowScopeExample2 string length in else part: 9
JDK 16
Flow scoping through logical operators AND (&&)
public class FlowScopeLogicalAndExample1 {
public static void main(String[] args) {
Object obj = "my string";
if (obj instanceof String s && s.length() > 3) {
System.out.println("string length in if part: " + s.length());
} else {
//s is not available here, compile error if uncommented next line
//System.out.println("string length in else part: "+s.length());
}
}
}
Output$ javac FlowScopeLogicalAndExample1.java $ java FlowScopeLogicalAndExample1 string length in if part: 9
JDK 16
public class FlowScopeLogicalAndExample2 {
public static void main(String[] args) {
Object obj = "my string";
if (obj instanceof String s && s.length() <= 3) {
System.out.println("string length in if part: " + s.length());
} else {
//s is not available here, compile error if uncommented next line
// System.out.println("string length in else part: "+s.length());
System.out.println("obj might not be String in else part, "
+ "no s available here");
}
}
}
Output$ javac FlowScopeLogicalAndExample2.java $ java FlowScopeLogicalAndExample2 obj might not be String in else part, no s available here
JDK 16
Multiple && operators
public class MultipleLogicalAndExample {
public static void main(String[] args) {
Object obj = "/a/b/c/";
if (obj instanceof String s && !s.isEmpty() &&
s.startsWith("/") && s.endsWith("/")) {
System.out.println("string length: " + s.length());
}
}
}
Output$ javac MultipleLogicalAndExample.java $ java MultipleLogicalAndExample string length: 7
JDK 16
Pattern Variable Does NOT Flow Through OR (||)
With OR, Java cannot guarantee the pattern was evaluated and matched, so it does not make sense the pattern variable should exist on the right side. Therefore, it's a compile time error if we use logical OR:
public class FlowScopeLogicalOrExample {
public static void main(String[] args) {
Object obj = "my string";
if (obj instanceof String s || s.length() > 3) {
System.out.println("string length in if part: " + s.length());
}
}
}
Output$ javac FlowScopeLogicalOrExample.java FlowScopeLogicalOrExample.java:4: error: cannot find symbol if (obj instanceof String s || s.length() > 3) { ^ symbol: variable s location: class FlowScopeLogicalOrExample FlowScopeLogicalOrExample.java:5: error: cannot find symbol System.out.println("string length in if part: " + s.length()); ^ symbol: variable s location: class FlowScopeLogicalOrExample 2 errors
JDK 16
More Scenarios of using multiple &&
Multiple && in equals method
import java.util.Objects;
public class MultipleLogicalAndExample2 {
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object other) {
return other instanceof Person person &&
this.age == person.age &&
Objects.equals(this.name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public static void main(String[] args) {
Person p1 = new Person("Jim", 22);
Person p2 = new Person("Jim", 22);
System.out.println(p1.equals(p2));
}
}
Output$ javac MultipleLogicalAndExample2.java $ java MultipleLogicalAndExample2 true
JDK 16
Using pattern matching in Ternary operator
import java.math.BigDecimal;
public class TernaryOperatorExample {
public static void main(String[] args) {
Number num = getNumber();
String s = num instanceof BigDecimal bd ? "BigDecimal: " +
bd.toPlainString() : "Not a BigDecimal";
System.out.println(s);
}
private static Number getNumber() {
return new BigDecimal("5");
}
}
Output$ javac TernaryOperatorExample.java $ java TernaryOperatorExample BigDecimal: 5
JDK 16
Multiple instanceof Pattern matching
public class MultipleInstanceOfExample {
public static void main(String[] args) {
Object input = getInput();
if (input instanceof Map<?, ?> map && map.containsKey("fruits")
&& map.get("fruits") instanceof List<?> list
&& list.size() > 1
&& list.stream().allMatch(
fruit -> fruit instanceof String fruitName
&& fruitName.length() > 1)) {
List<String> fruitList = list.stream()
.map(String::valueOf).toList();
System.out.printf("map size: %s, fruit list: %s%n",
map.size(),
String.join("|", fruitList));
}
}
private static Object getInput() {
return Map.of("fruits", List.of("apple", "mango"));
}
}
Output$ javac MultipleInstanceOfExample.java $ java MultipleInstanceOfExample map size: 1, fruit list: apple|mango
JDK 16
Expression-Pattern Type Compatibility
Compilation error: Expression type is same as Pattern type
Following will end up with compilation error:
public class SameTypeAsExpressionType {
public static void main(String... args) {
String str = "Hello, World!";
if (str instanceof String s) {
System.out.println("The length of the string is: " + s.length());
}
}
}
Output$ javac SameTypeAsExpressionType.java SameTypeAsExpressionType.java:4: error: pattern type String is a subtype of expression type String if (str instanceof String s) { ^ 1 error
JDK 16
Since myString is already declared as a String, the instanceof String s check is redundant and will always be true, defeating the operator's purpose of conditional type checking.
Expression Type vs Pattern Type
Pattern type is the type you're trying to match. Expression type is the type of the variable/expression you are testing. So in obj instanceof String s, String s is the pattern and String is the pattern type, whereas, Expression is on the left and the expression type is the type of obj.
Compilation error: Expression type is the subtype of Pattern type
Following will also end up with compilation error:
public class SubTypeOfExpressionType {
public static void main(String... args) {
String str = "Hello, World!";
if (str instanceof CharSequence s) {
System.out.println("The length of the string is: " + s.length());
}
}
}
Output$ javac SubTypeOfExpressionType.java SubTypeOfExpressionType.java:4: error: pattern type CharSequence is a subtype of expression type String if (str instanceof CharSequence s) { ^ 1 error
JDK 16
It is a compile-time error for a pattern instanceof expression to compare an expression of type S against a pattern of type T, where S is a subtype of T. This instanceof expression will always succeed and is then pointless.
Relaxed Expression-Pattern Type Compatibility since Java 21
Our last two examples failed to compile due to redundant instanceof pattern matching check. Starting Java 21, the compiler now accepts this code, allowing for more consistent use of pattern matching even when the type check is statically known to succeed.
public class SameTypeAsExpressionType {
public static void main(String... args) {
String str = "Hello, World!";
if (str instanceof String s) {
System.out.println("The length of the string is: " + s.length());
}
}
}
Output$ javac SameTypeAsExpressionType.java $ java SameTypeAsExpressionType The length of the string is: 13
JDK 21
public class SubTypeOfExpressionType {
public static void main(String... args) {
String str = "Hello, World!";
if (str instanceof CharSequence s) {
System.out.println("The length of the string is: " + s.length());
}
}
}
Output$ javac SubTypeOfExpressionType.java $ java SubTypeOfExpressionType.java The length of the string is: 13
JDK 21
As seen above we are able to compile the code in Java 21 (OpenJDK) and later version. However, I could not find a publically published OpenJDK document saying the subtype-restriction was removed in Java 21
|