Pattern Matching
instanceof Pattern Matching
When processing heterogeneous collections or API responses, you often need to check types and cast objects repeatedly. instanceof pattern matching combines type checking and casting into a single operation, eliminating redundant code and potential ClassCastException bugs.
Old vs New Style
OldVsNew.java
public class OldVsNew {
static void oldStyle(Object obj) {
if (obj instanceof String) {
String s = (String) obj; // redundant cast
System.out.println("OLD: " + s.toUpperCase());
}
}
static void newStyle(Object obj) {
if (obj instanceof String s) {
System.out.println("NEW: " + s.toUpperCase());
}
}
public static void main(String[] args) {
Object obj = "pattern matching";
oldStyle(obj);
newStyle(obj);
}
}
The pattern variable s is automatically cast and scoped to the if block.
pattern_variable
A pattern variable is automatically declared, cast, and scoped when the instanceof test succeeds.
Basic Usage
Basic.java
public class Basic {
public static void main(String[] args) {
Object obj = ;
if (obj instanceof String s) {
System.out.println("Length: " + s.length());
System.out.println("Upper: " + s.toUpperCase());
}
}
}
public class Basic {
public static void main(String[] args) {
Object obj = ;
if (obj instanceof String s) {
System.out.println("Length: " + s.length());
System.out.println("Upper: " + s.toUpperCase());
}
}
}
public class Basic {
public static void main(String[] args) {
Object obj = ;
if (obj instanceof String s) {
System.out.println("Length: " + s.length());
System.out.println("Upper: " + s.toUpperCase());
}
}
}
Benefits
- No redundant cast: type checking and casting happen together
- Shorter code: fewer lines, clearer intent
- Safer: compiler ensures the pattern variable is only used where valid
Scope Rules
The pattern variable is in scope where the instanceof test is definitely true:
Scope.java
public class Scope {
public static void main(String[] args) {
Object obj = ;
if (obj instanceof String s) {
System.out.println("In if: " + s);
}
if (obj instanceof String s && s.length() > 3) {
System.out.println("Length check: " + s);
}
if (!(obj instanceof String s)) {
} else {
System.out.println("In else: " + s);
}
}
}
public class Scope {
public static void main(String[] args) {
Object obj = ;
if (obj instanceof String s) {
System.out.println("In if: " + s);
}
if (obj instanceof String s && s.length() > 3) {
System.out.println("Length check: " + s);
}
if (!(obj instanceof String s)) {
} else {
System.out.println("In else: " + s);
}
}
}
scope_flow
Pattern variables follow "flow scoping" - they are in scope wherever the compiler can prove the pattern matched.
Null Safety
NullSafe.java
public class NullSafe {
static void process(Object obj) {
if (obj instanceof String s) {
System.out.println("String: " + s);
} else {
System.out.println("Not a string (or null)");
}
}
public static void main(String[] args) {
process("hello");
process(123);
process(null); // instanceof is false for null
}
}
Multiple Type Checks
MultipleTypes.java
public class MultipleTypes {
static String describe(Object obj) {
if (obj instanceof String s) {
return "String of length " + s.length();
} else if (obj instanceof Integer i) {
return "Integer value " + i;
} else if (obj instanceof Double d) {
return "Double value " + d;
} else {
return "Unknown type";
}
}
public static void main(String[] args) {
System.out.println(describe("hello"));
System.out.println(describe(42));
System.out.println(describe(3.14));
System.out.println(describe(true));
}
}
Exercise: Practical.java
Create a method that processes a list of Objects and returns formatted strings based on their actual types