How do you safely downcast a reference, and what does the instanceof operator give you (including pattern matching)?
answer
- instanceof tests the runtime type; null returns false
- Classic: if (x instanceof Dog) { Dog d = (Dog) x; }
- Pattern (16+): if (x instanceof Dog d) — binds d
- Flow scoping: && and negated-return keep the binding; || does not
- Long instanceof chains → prefer polymorphism or sealed switch
basics
~20 sBefore casting a parent-typed reference to a child type, check it with instanceof. If the check passes, the cast is safe. Modern Java lets you do both at once: if (x instanceof Dog d) binds d so you skip the separate cast.
solid answer
~50 sTo downcast safely you confirm the object's real type first with the instanceof operator, which returns true only if the runtime object is an instance of the named type (and false for null). Classic form: check, then cast — if (a instanceof Dog) { Dog d = (Dog) a; ... }. Since Java 16, pattern matching for instanceof combines the test and the cast: if (a instanceof Dog d) { ... } binds d to the already-cast value, eliminating the redundant explicit cast and a class of bugs. The bound variable obeys flow scoping: it is in scope only where the compiler can prove the test was true (the if body, or after a negated early return). You can also add no extra cast in a switch using type patterns. Skipping the guard and casting blindly risks ClassCastException; over-relying on instanceof chains can signal that polymorphism or sealed-type pattern switches would model the problem better.
go deeper
Knows to use instanceof before casting and that null instanceof anything is false.
Writes guarded downcasts, uses pattern-matching instanceof, and understands basic flow scoping of the binding variable.
Explains flow scoping precisely (&&, negated returns vs ||), reaches for pattern switch / sealed types, and recognizes instanceof chains as a refactoring signal toward polymorphism.
Designs APIs so clients rarely need instanceof, decides when a closed sealed hierarchy with exhaustive pattern switches beats virtual dispatch, and weighs evolvability (adding subtypes vs adding operations).
## The problem instanceof solves When you hold a reference typed as a supertype (say `Object` or `Animal`) but need behavior that only exists on a subtype, you must **downcast**. A blind downcast `(Dog) a` throws **`ClassCastException`** if the object isn't actually a `Dog`. The `instanceof` operator lets you *ask first*. ## What instanceof evaluates `expr instanceof Type` is a boolean test of the **runtime type** of the object `expr` points to: - returns `true` if the object's actual class is `Type` **or any subtype** of `Type` (or it implements `Type` when `Type` is an interface); - returns `false` otherwise; - returns `false` for `null` — `null instanceof anything` is always `false`. This is convenient: the test doubles as a null guard. ```java Object o = "hello"; o instanceof String // true o instanceof Integer // false null == o ? ... : ... // (o is not null here) ``` ## Classic guarded downcast The traditional safe pattern is *test, then cast*: ```java if (a instanceof Dog) { Dog d = (Dog) a; // guaranteed to succeed d.fetch(); } ``` This works but repeats the type name three times and leaves a window for the redundant cast to drift out of sync. ## Pattern matching for instanceof (Java 16+) A **type pattern** fuses the test and the cast and introduces a **pattern (binding) variable**: ```java if (a instanceof Dog d) { // tests, and on success binds d = (Dog) a d.fetch(); } ``` No separate cast, no repeated type name. The binding `d` is effectively final and already of type `Dog`. ### Flow scoping The binding variable is in scope **only where the compiler can prove the pattern matched**: ```java if (!(a instanceof Dog d)) return; // negated: if we get past here, match was true d.fetch(); // d IS in scope below the early return ``` ```java if (a instanceof Dog d && d.isHungry()) { ... } // && short-circuits; d usable on the right ``` With `||` the binding is **not** available afterward, because reaching the next clause doesn't guarantee a match: ```java if (a instanceof Dog d || d.bark()) { ... } // compile error: d not definitely assigned ``` ## Type patterns in switch (Java 21+) For a fixed set of subtypes, a **pattern switch** replaces an `if/else instanceof` chain: ```java String describe(Animal a) { return switch (a) { case Dog d -> "dog: " + d.breed(); case Cat c -> "cat: " + c.name(); default -> "unknown"; }; } ``` With a **sealed** hierarchy the compiler can even verify the switch is *exhaustive* and let you drop the `default`. ## When NOT to reach for instanceof A long `instanceof`/cast chain often means you're hand-rolling dispatch the language already gives you. Prefer: - **Polymorphism**: put a virtual method on the supertype and override it per subtype, so callers don't inspect types at all. - **Sealed types + pattern switch** when the set of subtypes is genuinely closed and you need per-type logic *outside* the classes. Use `instanceof` for legitimate type narrowing at boundaries (deserialization, `equals`, generic `Object` APIs), not as a substitute for designing the hierarchy.
- Why is the pattern variable from `if (a instanceof Dog d || ...)` not usable after the if?Because of flow scoping: with ||, control can reach the rest of the expression (and beyond) even when the match was false, so the compiler cannot guarantee d was assigned. Only paths where the match is provably true see the binding.
- How does a sealed hierarchy improve an instanceof/cast chain?A sealed supertype enumerates all permitted subtypes, so a pattern switch over them can be checked for exhaustiveness — the compiler errors if a case is missing and you can omit default, catching new subtypes at compile time.
saying these in an interview costs you the question
- Believing null instanceof Type returns true — it is always false.
- Casting after a *failed* instanceof anyway, expecting null instead of an exception.
- Thinking the pattern variable is in scope everywhere regardless of flow (it obeys flow scoping).
- Treating instanceof as the normal way to do dispatch instead of overriding a method.