• 🗼 Java Pattern Matching with instanceof

    — Paris Monuments Edition

    Pattern matching in Java lets you check whether an object fits a specific structure (like a class), and if it does, automatically extract information from it. It improves clarity, safety, and reduces boilerplate code — especially when used with the instanceof operator.

    Introduced officially in JEP 394, pattern matching for instanceof is available from Java 16 onwards.

    🧠 What Is Pattern Matching?

    Traditionally, when you wanted to work with a specific subtype of an object, you needed to:

    1️⃣ Test the type with instanceof

    2️⃣ Cast it to the target class

    3️⃣ Use its specific fields or methods

    Let’s illustrate this with a theme of Paris monuments! We'll model some famous landmarks with a Monument interface.

    🏛️ Traditional instanceof Usage

    public interface Monument {
        static String getHeightDescription(Monument m) {
            if (m instanceof EiffelTower) {
                EiffelTower e = (EiffelTower) m;
                return "Eiffel Tower is " + e.height() + " meters tall.";
            } else if (m instanceof ArcDeTriomphe) {
                ArcDeTriomphe a = (ArcDeTriomphe) m;
                return "Arc de Triomphe is " + a.height() + " meters tall.";
            } else {
                throw new IllegalArgumentException("Unknown monument");
            }
        }
    }
    
    class EiffelTower implements Monument {
        final double height;
        EiffelTower(double height) { this.height = height; }
        double height() { return height; }
    }
    
    class ArcDeTriomphe implements Monument {
        final double height;
        ArcDeTriomphe(double height) { this.height = height; }
        double height() { return height; }
    }

    This code works but requires explicit casting, which is repetitive and error-prone.

    ✨ Enter Pattern Matching

    Pattern matching lets you streamline the code and safely access subclass data without casting:

    public static String getHeightDescription(Monument m) {
        if (m instanceof EiffelTower e) {
            return "Eiffel Tower is " + e.height() + " meters tall.";
        } else if (m instanceof ArcDeTriomphe a) {
            return "Arc de Triomphe is " + a.height() + " meters tall.";
        } else {
            throw new IllegalArgumentException("Unknown monument");
        }
    }

    Notice how we:

    🟣 Test the type

    🟣 Declare a variable (e or a) of the right type

    🟣 Access the data without casting

    Shorter, cleaner, safer.

    🧩 Breakdown of a Pattern

    Consider this pattern:

    m instanceof EiffelTower e

    It consists of:

    🟣 Predicate: instanceof EiffelTower – checks the type

    🟣 Target: m – the object being tested

    🟣 Pattern variable: e – available if the test passes

    🧭 Scope of Pattern Variables

    Pattern variables (like e and a) only exist when the instanceof check is true.

    public static boolean isTallMonument(Monument m) {
        if (!(m instanceof EiffelTower e)) {
            return false; // e not in scope here
        }
        return e.height() > 300; // e is safely in scope here
    }

    They can also be used directly in complex conditions:

    if (m instanceof EiffelTower e && e.height() > 300) {
        System.out.println("A very tall Eiffel Tower!");
    }

    But be careful — this is not allowed:

    if (m instanceof EiffelTower e || e.height() > 300) { // ❌ Compile error
        // e might not be initialized if instanceof is false
    }

    📌 Summary

    Pattern matching with instanceof:

    🟣 Reduces casting

    🟣 Makes your code cleaner and more maintainable

    🟣 Prevents accidental bugs due to mismatched types

    🟣 Is especially helpful when working with complex object models — or iconic monuments!