library
intermediatePattern Matching for instanceof (Java 16)
Eliminate explicit casts after instanceof checks using pattern variables.
The type test and the cast become one step, and the variable is bound for you.
Like a customs officer who checks your passport (instanceof) and stamps it (cast) in one step, handing back a 'verified citizen' badge (pattern variable) you can use inside the country (true scope).
Key Concepts
1
// before
if (obj instanceof String) {
String s = (String) obj;
use(s);
}2
// after
if (obj instanceof String s) {
use(s);
}3
The cast cannot disagree with the test any more. That was the bug the old form allowed: two type names,
and nothing checking that they matched.
4
Where the variable is in scope follows the flow, which is the part worth learning:
5
if (o instanceof String s) { /* s here */ }
else { /* NOT here */ }6
if (o instanceof String s && s.length() > 3) { } // s after && -- yes7
if (o instanceof String s || s.isEmpty()) { } // after || -- no8
The || case is rejected because reaching the right-hand side means the match failed, so there is no s.
||s
9
Negation flips it. This is the useful idiom:
10
void f(Object o) {
if (!(o instanceof String s)) return;
use(s); // s is in scope for the rest of the method
}11
The match must have succeeded for execution to get past the return. So the compiler keeps s alive, which
removes the nesting the old form forced.
returns
12
It makes equals a one-liner:
@Override
public boolean equals(Object o) {
return o instanceof Point p && x == p.x && y == p.y;
}equals
13
instanceof is false for null, so the null check is handled too.
instanceofnull
14
Record patterns go further, in Java 21, destructuring as they match:
15
if (o instanceof Point(int x, int y)) {
use(x + y);
}16
And in a switch over a sealed type the same patterns give exhaustive, type-safe dispatch. That is what
finally replaced long instanceof chains, and the visitor pattern with them.
instanceof
17
What the interviewer is probing.1. "What does the pattern variable form prevent?" Probing: the bug it removes. Stalls: "It
is shorter." Moves up: the cast and the test can no longer disagree — the old form named the type
twice with nothing checking they matched.
18
2. "Where is the variable in scope with && versus ||?" Probing: flow scoping. Stalls: "In
the whole if." Moves up: after && it is in scope because the match succeeded; after || it is not,
because reaching the right-hand side means it failed.
19
3. "Why can you use the variable after if (!(o instanceof String s)) return;?" Probing:
negation and flow scoping. Stalls: "You cannot." Moves up: execution only passes the return if
the match succeeded, so the compiler keeps it in scope for the rest of the method.
20
4. "How does this simplify equals?" Probing: a practical application. Stalls: "It does not."
Moves up: return o instanceof Point p && x == p.x && y == p.y — one line, with the null check
handled because instanceof is false for null.