1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Pattern matching (Java 16+) simplifies type testing and casting. instanceof (Java 16): if (obj instanceof String s) combines test + cast. switch (Java 17+): Type patterns in switch, guarded patterns (case String s when s.length() > 5), exhaustiveness checking.
- Record patterns (Java 19+): Deconstruct records (case Point(var x, var y)).
- Benefits: Less boilerplate, no ClassCastException, type-safe.
- Replaces: instanceof + manual cast (3 lines → 1).
- Use for: Polymorphic dispatch, visitor pattern, JSON parsing!
Think of airport security scanner. Before = manual check ("Is this a liquid? OK, now measure it"). After pattern matching = smart scanner ("Liquid detected AND over 100ml → confiscate"). One step test + extract info!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Old instanceof: Check ID, then ask for name separately
- Pattern matching: Smart ID scanner (reads type AND extracts info in one scan)
3. Technical Mastery (The "Deep Dive")
Pattern Matching Evolution
| Java Version | Feature |
|---|---|
| Java 16 | Pattern matching for instanceof |
| Java 17 | Pattern matching for switch |
| Java 19 | Record patterns |
| Java 21 | Pattern matching enhancements |
4. Interactive & Applied Code
java
import java.util.*;
public class PatternMatchingDemo {
public static void main(String[] args) {
demonstrateInstanceof();
demonstrateSwitchPatterns();
demonstrateRecordPatterns();
demonstrateGuardedPatterns();
}
// ❌ BEFORE: Traditional instanceof (verbose, error-prone)
static void oldInstanceof(Object obj) {
if (obj instanceof String) {
String s = (String) obj; // Manual cast
System.out.println("String length: " + s.length());
}
}
// ✅ AFTER: Pattern matching instanceof (concise, safe)
static void newInstanceof(Object obj) {
if (obj instanceof String s) { // Test + cast in one!
System.out.println("String length: " + s.length());
}
}
static void demonstrateInstanceof() {
System.out.println("=== PATTERN MATCHING INSTANCEOF ===");
Object obj = "Hello";
// Pattern variable scope
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // s in scope
}
// System.out.println(s); // ❌ ERROR: s out of scope
// Negation
if (!(obj instanceof String s)) {
System.out.println("Not a string");
} else {
System.out.println("String: " + s); // s in scope in else!
}
// Complex conditions
if (obj instanceof String s && s.length() > 3) {
System.out.println("Long string: " + s);
}
}
// Pattern matching for switch
static void demonstrateSwitchPatterns() {
System.out.println("\n=== SWITCH PATTERNS ===");
Object obj = 42;
// ❌ BEFORE: Nested instanceof
String result1;
if (obj instanceof Integer) {
result1 = "Integer: " + obj;
} else if (obj instanceof String) {
result1 = "String: " + obj;
} else if (obj instanceof Double) {
result1 = "Double: " + obj;
} else {
result1 = "Unknown";
}
// ✅ AFTER: Pattern matching switch
String result2 = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case Double d -> "Double: " + d;
case null -> "null";
default -> "Unknown";
};
System.out.println(result2);
}
// Guarded patterns (when clause)
static void demonstrateGuardedPatterns() {
System.out.println("\n=== GUARDED PATTERNS ===");
Object obj = "Hello";
String result = switch (obj) {
case String s when s.length() < 5 -> "Short string: " + s;
case String s when s.length() >= 5 -> "Long string: " + s;
case Integer i when i > 0 -> "Positive: " + i;
case Integer i -> "Non-positive: " + i;
case null -> "null";
default -> "Other";
};
System.out.println(result);
}
// Record patterns (Java 19+)
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
static void demonstrateRecordPatterns() {
System.out.println("\n=== RECORD PATTERNS ===");
Object obj = new Point(3, 4);
// Deconstruct record
if (obj instanceof Point(int x, int y)) {
System.out.println("Point: x=" + x + ", y=" + y);
}
// Nested record patterns
Object shape = new Circle(new Point(0, 0), 5);
if (shape instanceof Circle(Point(int x, int y), int r)) {
System.out.println("Circle at (" + x + "," + y + ") with radius " + r);
}
// Switch with record patterns
String description = switch (obj) {
case Point(int x, int y) when x == 0 && y == 0 -> "Origin";
case Point(int x, int y) -> "Point at (" + x + "," + y + ")";
default -> "Not a point";
};
System.out.println(description);
}
}
// Real-world example: Expression evaluator
sealed interface Expr {}
record Num(int value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
class ExpressionEvaluator {
// ✅ Pattern matching makes this elegant!
static int eval(Expr expr) {
return switch (expr) {
case Num(int value) -> value;
case Add(Expr left, Expr right) -> eval(left) + eval(right);
case Mul(Expr left, Expr right) -> eval(left) * eval(right);
};
}
public static void main(String[] args) {
// (2 + 3) * 4
Expr expr = new Mul(
new Add(new Num(2), new Num(3)),
new Num(4)
);
System.out.println("Result: " + eval(expr)); // 20
}
}
// JSON-like structure parsing
sealed interface JsonValue {}
record JsonString(String value) implements JsonValue {}
record JsonNumber(double value) implements JsonValue {}
record JsonArray(List<JsonValue> values) implements JsonValue {}
class JsonParser {
static String format(JsonValue json) {
return switch (json) {
case JsonString(String s) -> "\"" + s + "\"";
case JsonNumber(double n) -> String.valueOf(n);
case JsonArray(List<JsonValue> values) ->
values.stream()
.map(JsonParser::format)
.collect(java.util.stream.Collectors.joining(", ", "[", "]"));
};
}
}5. The Comparison & Decision Layer
| Before Pattern Matching | After Pattern Matching |
|---|---|
if (obj instanceof String) + cast | if (obj instanceof String s) |
| Nested if-else | switch with type patterns |
| Manual record field access | Deconstruct with record patterns |
| ClassCastException risk | Type-safe |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "What's the scope of pattern variables?" Answer: Flow-scoped (available when compiler knows type is matched)!
java
if (obj instanceof String s) {
System.out.println(s); // ✅ In scope
}
// System.out.println(s); // ❌ Out of scope
// Special case: negation
if (!(obj instanceof String s)) {
// s NOT in scope here (obj might not be String)
} else {
System.out.println(s); // ✅ In scope (must be String)
}
// With conditions
if (obj instanceof String s && s.length() > 3) {
// ✅ s in scope (short-circuit AND)
}
if (obj instanceof String s || otherCondition) {
// ❌ s NOT in scope (OR doesn't guarantee instanceof)
}Pro-Tip: Exhaustiveness with sealed types:
java
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
// ✅ No default needed (exhaustive!)
double area(Shape shape) {
return switch (shape) {
case Circle(double r) -> Math.PI * r * r;
case Square(double s) -> s * s;
// Compiler knows these are ALL types!
};
}