Java 16 Pattern Matching for instanceof
Introduction
Every Java developer has written this sequence many times: test whether an object is of some type with
OUTPUT of the above code
Explanation of the code
Flow scoping
A pattern variable is in scope exactly where the compiler can be sure the match succeeded. This is called flow scoping, and it works with negation too: if a method returns early when the match fails, the pattern variable is in scope for the rest of the method -
OUTPUT of the above code
The same reasoning makes
OUTPUT of the above code
The whole method is one expression:
Rules the compiler enforces
OUTPUT on compiling the above code
Note - pattern matching for instanceof was the first step of a larger plan. Pattern matching for switchClick to read: Pattern matching for switch and record patternsClick to read: record patterns, final in Java 21, extend the same idea to
instanceof, then cast it to that type, then use it - naming the type twice and adding a variable just to hold the cast result. Pattern matching for instanceof, previewed in Java 14 and 15 and finalized in Java 16Click to read tutorial: Overview of New Features in Java 16 (JEP 394), does all three in one step. This tutorial explains pattern matching for instanceof with examples. We will see the new syntax, how the scope of the pattern variable follows the logic of the code (flow scoping), how it simplifies equals() methods, and the rules the compiler enforces.
The test-then-cast problem
Before and after pattern matching
//InstanceofDemo.java
public class InstanceofDemo {
static String describeOld(Object obj) {
if (obj instanceof String) {
String s = (String) obj; // test, then cast to a new variable
return "String of length " + s.length();
}
return "something else";
}
static String describe(Object obj) {
if (obj instanceof String s) { // test and bind in one step
return "String of length " + s.length();
} else if (obj instanceof Integer i && i > 100) { // s is not in scope here; i can be used in the same condition
return "large Integer " + i;
} else if (obj instanceof int[] arr) {
return "int array with " + arr.length + " elements";
}
return "something else: " + obj;
}
public static void main(String[] args) {
System.out.println(describeOld("hello"));
for (Object o : new Object[] {"hello", 250, 7, new int[] {1, 2, 3}}) {
System.out.println(describe(o));
}
}
}
String of length 5 String of length 5 large Integer 250 something else: 7 int array with 3 elements
obj instanceof String sis a type pattern. Ifobjis aString, the test is true andobj, already cast, is assigned to the new pattern variables.- In
obj instanceof Integer i && i > 100,ican be used on the right of&&, because that part only runs when the match succeeded. So 250 is a "large Integer", while 7 fails the condition and falls through to the end. - Patterns work with any reference type, including arrays such as
int[]. Ifobjisnull,instanceofis false, as before, so a pattern variable is never null.
Using a pattern variable after a negated test
//FlowScopingDemo.java
public class FlowScopingDemo {
static int length(Object obj) {
if (!(obj instanceof String s)) {
return -1; // not a String: leave the method
}
return s.length(); // here obj must be a String, so s is in scope
}
public static void main(String[] args) {
System.out.println(length("pattern"));
System.out.println(length(42));
}
}
7 -1
s unavailable after || - in obj instanceof String s || s.isEmpty(), the right-hand side runs exactly when the match failed, so using s there is a compile error.
Simpler equals() methods
Pattern matching is especially welcome in equals(), which always tests and casts its argument -
equals() with pattern matching
//EqualsDemo.java
import java.util.Objects;
public class EqualsDemo {
static final class Book {
private final String isbn;
private final String title;
Book(String isbn, String title) { this.isbn = isbn; this.title = title; }
@Override
public boolean equals(Object o) {
return o instanceof Book other
&& isbn.equals(other.isbn)
&& title.equals(other.title);
}
@Override public int hashCode() { return Objects.hash(isbn, title); }
}
public static void main(String[] args) {
Book a = new Book("978-0134685991", "Effective Java");
System.out.println(a.equals(new Book("978-0134685991", "Effective Java")));
System.out.println(a.equals("Effective Java"));
System.out.println(a.equals(null));
}
}
true false false
o instanceof Book other handles null, the type check and the cast at once.
- A pattern variable cannot have the same name as another variable in scope, such as a local variable or a parameter.
- Using a pattern variable where the match may not have succeeded is an error.
- The test must be able to fail. In Java 16, testing an expression against a type it always has, such as a
StringagainstString s, is an error, since the pattern would be pointless. (Java 21 relaxed this rule when it added pattern matching for switch.) - Testing against an impossible type, such as an
IntegeragainstString s, is an error, as it already was for plaininstanceof.
Compiler errors for pattern variables
//PatternErrors.java
public class PatternErrors {
static void check(Object obj, String text) {
if (obj instanceof String text) { } // name clash with a parameter
if (obj instanceof String s || s.isEmpty()) { } // s may not be bound here
if (text instanceof String t) { } // always true
}
}
PatternErrors.java:4: error: variable text is already defined in method check(Object,String)
if (obj instanceof String text) { } // name clash with a parameter
^
PatternErrors.java:5: error: cannot find symbol
if (obj instanceof String s || s.isEmpty()) { } // s may not be bound here
^
symbol: variable s
location: class PatternErrors
PatternErrors.java:6: error: unconditional patterns in instanceof are not supported in -source 16
if (text instanceof String t) { } // always true
^
(use -source 21 or higher to enable unconditional patterns in instanceof)
3 errors
switch and to taking records apart.
Summary
In this tutorial we saw how pattern matching for instanceof, final in Java 16, replaces the test-then-cast idiom with a type pattern which tests, casts and binds a variable in one step. We used pattern variables in conditions with &&, saw how flow scoping makes a variable available after a negated test, and wrote an equals() method as a single expression. Lastly, we went through the rules the compiler enforces, with the errors for breaking them. Next, read about the Java 16 API additionsClick to read tutorial: Java 16 API Additions: Stream.toList, mapMulti and More.Continue Reading Java 12 to 17 Tutorials
Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 16 Records Tutorial with ExamplesClick to read tutorial: Java 16 Records Tutorial with Examples Java 16 API Additions: Stream.toList, mapMulti and MoreClick to read tutorial: Java 16 API Additions: Stream.toList, mapMulti and More jpackage Tutorial: Packaging Java Apps (Java 16)Click to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16) Strong Encapsulation of JDK Internals in Java 16 and 17Click to read tutorial: Strong Encapsulation of JDK Internals in Java 16 and 17 Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15
Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 16 Records Tutorial with ExamplesClick to read tutorial: Java 16 Records Tutorial with Examples Java 16 API Additions: Stream.toList, mapMulti and MoreClick to read tutorial: Java 16 API Additions: Stream.toList, mapMulti and More jpackage Tutorial: Packaging Java Apps (Java 16)Click to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16) Strong Encapsulation of JDK Internals in Java 16 and 17Click to read tutorial: Strong Encapsulation of JDK Internals in Java 16 and 17 Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15