Lesson Completion
Back to course

Pattern Matching: Simplified Type Testing and Casting

Beginner
12 minutes4.6Java

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 VersionFeature
Java 16Pattern matching for instanceof
Java 17Pattern matching for switch
Java 19Record patterns
Java 21Pattern 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 MatchingAfter Pattern Matching
if (obj instanceof String) + castif (obj instanceof String s)
Nested if-elseswitch with type patterns
Manual record field accessDeconstruct with record patterns
ClassCastException riskType-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! }; }

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01