Pattern Matching in Modern Java (instanceof and switch)
One of the features that was introduced in Java 16 is Pattern Matching for instanceof and was finalized in Java 21.
1 - Pattern Matching for instanceof:
In the old way, when we needed to check an object's type, we required a conditional check followed by an explicit, manual cast. Pattern matching combines these two steps into an atomic operation.
Example:
// Traditional Java (Boilerplate-heavy)
if (obj instanceof String) {
String s = (String) obj; // Manual cast required
System.out.println(s.toUpperCase());
}
// Modern Java (Pattern Matching)
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
// 's' is automatically cast and ready
}
Flow Scoping:
The binding of variable(s) uses flow scoping. It is only in scope where the compiler can deduce that the type check has succeeded.
if(obj instanceof String s && s.length()>5) (The right side of the && only executes if the check is true); in case of ||, if the left side is false, it will not compile because s is undefined.
2 - Pattern Matching for switch(Java21+):
Before Java 21, switch was limited to primitives and boxed types (In Java, boxed types (or wrapper classes) are reference data types that wrap primitive data types into full-fledged objects. They are required because primitive types (like int or char) lack object-oriented capabilities, meaning they cannot be assigned null or used inside Java collections like ArrayList or HashMap), enums, and Strings. You can now switch directly on the type of an object, without the need for long if-else-if blocks.
NB:
In Java, primitive types are raw, lightweight values stored directly on the stack (e.g., int, char, boolean), while boxed types (wrapper classes) are full-fledged objects stored on the heap that encapsulate those primitives (e.g., Integer, Character, Boolean).
Example of use:
public static String formatObject(Object obj) {
return switch (obj) {
case Integer i -> "An integer: " + i;
case String s -> "A string of length " + s.length();
case Long l -> "A long value";
default -> "Unknown type";
};
}
Safe Null Handling:
In the old way, passing a null value into a switch statement immediately threw a NullPointerException.
Modern Java lets you handle null values cleanly as a standard case label.
switch (obj) {
case null -> System.out.println("Object is null!");
case String s -> System.out.println("String: " + s);
default -> System.out.println("Something else");
}
Guarded Patterns (when clauses)
You can append a when clause to a case label to add extra conditional logic. This allows fine-grained filtering directly inside the switch infrastructure.
switch (obj) {
case String s when s.equalsIgnoreCase("admin") -> grantFullAccess();
case String s when s.length() > 10 -> handleLongString(s);
case String s -> handleNormalString(s); // Catch-all for String
default -> denyAccess();
}
NB:
Case order matters. The compiler evaluates from top to bottom. Specific patterns with guards must be declared above generic type patterns.
Record Patterns and Deconstruction (Java 21+)
If you use Java Records, you can destructure (take apart) the record components directly inside the pattern match, removing the need to call getter methods.
public record Point(int x, int y) {}
public static void printPoint(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println("Coordinates: " + x + ", " + y);
}
}
switch expression covers every permitted subclass, you do not need a default caseBike subclass to the Vehicle interface later, the code will fail to compile until the switch expression is updated, preventing silent production errors..jpg)
Comments
Post a Comment