Pattern Matching (instanceof, switch, record patterns) practice questions

From OCP Java SE 25 (1Z0-831) · 25 questions on this topic

Pattern Matching (instanceof, switch, record patterns) practice questions from OCP Java SE 25 (1Z0-831). This pack has 25 questions tagged Pattern Matching (instanceof, switch, record patterns), drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Pattern Matching (instanceof, switch, record patterns)

  1. Question 1

    One of the `User` values passed to `f` carries a null `Name` component. What is the result of running this program? ```java public class Main { record Name(String first, String last) {} record User(Name name, int age) {} static String f(Object o) { return switch (o) { case User(Name(var first, _), var age) when age >= 18 -> "adult:" + first; case User(Name n, _) -> "minor:" + n.last(); case null, default -> "none"; }; } public static void main(String[] args) { System.out.println(f(new User(new Name("Ann", "Lee"), 20)) + "|" + f(new User(null, 30)) + "|" + f(new User(new Name("Bo", "Kim"), 12))); } } ```

    1. A. Throws NullPointerExceptionCorrect answer

      A nested record pattern never matches a null component, so the user with a null name skips the guarded case, but the following nested type pattern is unconditional, binds the null, and calling a method on it throws NullPointerException.

    2. B. adult:Ann|none|minor:Kim

      Assumes the user with a null name falls through to the default; the unconditional type pattern matches it and then dereferences null.

    3. C. adult:Ann|minor:null|minor:Kim

      Assumes the unconditional type pattern also rejects null; a type pattern of the component's declared type performs no null check, so it binds null.

    4. D. Compilation fails

      Assumes the patterns or the unnamed pattern are illegal; record patterns, type patterns, and the unnamed pattern are all valid, so it compiles.

    Explanation

    A nested record pattern includes an implicit null check and does not match a null component, whereas a nested type pattern whose type is the component's declared type is unconditional and binds even a null value without testing it. Routing a null component into an unconditional type pattern therefore binds null, and using that binding throws at run time. The unnamed pattern matches any component without binding it.

  2. Question 2

    Does this compile, and if so what does it print? ```java public class Main { public static void main(String[] args) { Object o = "x"; boolean b = o instanceof String s || s.isEmpty(); System.out.println(b); } } ```

    1. A. true

      A true result would require the program to compile and run, but it never reaches execution.

    2. B. false

      There is no runtime boolean result at all, because compilation fails first.

    3. C. Compilation fails: s is not in scope on the right-hand side of ||Correct answer

      The right operand of || runs only when the left match failed, so the binding is definitely unassigned there; referencing it is a compile error.

    4. D. Throws NullPointerException

      The failure is at compile time, not a runtime NullPointerException.

    Explanation

    The && operator extends a pattern variable's scope to its right operand, but || does the opposite: the right side of || runs precisely when the left match failed, so the binding is definitely unassigned and unavailable there. Referencing it after || is therefore a compile-time error, not any runtime result.

  3. Question 3

    What does this print? ```java public class Main { record Box<T>(T content) {} static <T> String f(Box<T> box) { return switch (box) { case Box<T>(var c) -> "got:" + c; }; } public static void main(String[] args) { System.out.println(f(new Box<>("hi"))); } } ```

    1. A. Compilation fails: generic record patterns are not allowed

      Record patterns may be generic; the compiler can infer or check the type argument (JEP 440).

    2. B. got:hiCorrect answer

      The generic record pattern's type argument matches the selector's static type, so it is total and the switch is exhaustive; it matches, binds the content to "hi", and prints got:hi.

    3. C. Compilation fails: Box<T> cannot appear as a case label

      A parameterized record type is a legal case label; only a raw or unrelated type would be a problem.

    4. D. got:null

      The component holds "hi", not null; the var binding captures the actual content.

    Explanation

    When a record pattern's type argument matches the selector's type, the pattern is total for that selector, so it alone makes the switch exhaustive with no default. Generic record patterns are legal case labels, and here the pattern matches the value and binds its content component.

  4. Question 4

    `Shape` is a sealed interface with exactly two permitted records, but the selector of the switch is a `Box`, which is not sealed. The switch has no `default` label. What is the result of compiling and running this code? ```java public class Main { sealed interface Shape permits Circle, Square {} record Circle(double r) implements Shape {} record Square(double side) implements Shape {} record Box(Shape shape) {} static String f(Box b) { return switch (b) { case Box(Circle c) -> "circle:" + c.r(); case Box(Square s) -> "square:" + s.side(); }; } public static void main(String[] args) { System.out.println(f(new Box(new Square(2.0)))); } } ```

    1. A. Compilation fails: a switch expression always requires a default label

      This is the pre-Java-21 rule for reference selectors; a switch expression needs only to be exhaustive, and covering a sealed hierarchy is one of the ways to be exhaustive, so no default is required.

    2. B. Compilation fails: the switch is not exhaustive, because the selector type Box is not sealed

      Assumes exhaustiveness looks only at the selector's own type hierarchy; it also looks inside record patterns — a record type has exactly one shape, so the two component patterns for Circle and Square cover the sealed Shape, making the switch exhaustive for Box.

    3. C. square:2.0Correct answer

      Exhaustiveness for record patterns is computed recursively: every Box holds one Shape, sealed to Circle and Square, so the two patterns cover every Box without a default. At run time Box(Square(2.0)) matches the second pattern and s.side() is the double 2.0, which concatenates as `square:2.0`.

    4. D. square:2

      Assumes the double component prints without its fractional part; side() returns a double and Double.toString(2.0) is `2.0`, not `2`.

    Explanation

    Trace: exhaustiveness for record patterns is computed *recursively*, component by component. Every `Box` has exactly one `Shape`, and `Shape` is sealed to `Circle` and `Square`, so the pair of patterns `Box(Circle c)` and `Box(Square s)` between them cover every possible `Box` — the set of component patterns is exhaustive for `Shape`, therefore the switch is exhaustive for `Box`. No `default` is needed and none is wanted. At runtime the selector is a `Box(Square(2.0))`, the second pattern matches, `s.side()` is the `double` 2.0, and string concatenation renders it as `2.0`, printing `square:2.0`. Why the others are wrong: `Compilation fails: the switch is not exhaustive, because the selector type Box is not sealed` assumes exhaustiveness looks only at the selector's own type hierarchy. It also looks *inside* record patterns: a record type has exactly one shape, so the question becomes whether the component patterns are exhaustive for the component types — and here they are. `Compilation fails: a switch expression always requires a default label` is the pre-Java-21 rule for reference selectors. A switch expression needs only to be exhaustive, and covering a sealed hierarchy is one of the ways to be exhaustive. `square:2` assumes the component prints without its fractional part. `side()` returns a `double`, and `Double.toString(2.0)` is `2.0`, not `2`. Exam tip: nesting a sealed hierarchy inside a record pattern keeps you `default`-free, and that is the point — add a third permitted `Shape` and this switch stops compiling, which is exactly the compile-time nudge you want. Reverse trap: had `Box` been declared `record Box(Object shape)`, the same two cases would *not* be exhaustive and the switch would fail to compile without a `default`.

  5. Question 5

    What does this print? ```java public class Main { record Point(int x, int y) {} record Seg(Point a, Point b) {} static int f(Object o) { return switch (o) { case Seg(Point(var x1, var y1), Point(var x2, var y2)) -> (x2 - x1) + (y2 - y1); default -> -1; }; } public static void main(String[] args) { System.out.println(f(new Seg(new Point(1, 2), new Point(5, 8)))); } } ```

    1. A. -1

      -1 is the default arm, but the argument is a Seg whose components are Points, so the whole nested pattern matches.

    2. B. 4

      4 is only the x-term (x2 - x1); the y-term is dropped.

    3. C. 6

      6 is only the y-term (y2 - y1); the x-term is dropped.

    4. D. 10Correct answer

      The nested pattern binds x1=1, y1=2, x2=5, y2=8, so the arm computes (5 - 1) + (8 - 2) = 4 + 6 = 10.

    Explanation

    A nested record pattern matches only when every level matches, deconstructing the outer record into its inner records and then into var-inferred ints bound as ordinary locals. Tracing the bindings position by position from the outside in and applying the arithmetic gives the combined result.

  6. Question 6

    What does this print? ```java public class Main { static String f(Object o) { return switch (o) { case Integer i when i > 0 -> "pos"; case Integer i -> "nonpos"; case null, default -> "other"; }; } public static void main(String[] args) { System.out.println(f(-2) + " " + f(null) + " " + f("x")); } } ```

    1. A. pos other other

      The first call would need the guard i > 0 to hold for -2, but -2 is not positive, so this first token is wrong.

    2. B. nonpos nonpos other

      The second call passes null, which is handled by the combined null-and-default label, not by an Integer label, so the second token is other, not nonpos.

    3. C. Throws NullPointerException

      A null label is present (fused into case null, default), so the null selector is handled instead of throwing.

    4. D. nonpos other otherCorrect answer

      f(-2) matches the guarded Integer label but its guard is false, falling through to the unguarded Integer label (nonpos); f(null) is caught by case null, default (other); and f("x") matches no Integer label, also falling to case null, default (other).

    Explanation

    A guarded label is skipped when its guard is false, so matching falls through to the next label rather than to default. The combined case null, default is a single legal label that catches both null and everything otherwise unmatched, so a null argument is handled normally instead of throwing. Tracing the three calls gives a non-positive integer result, then two defaulted results.

  7. Question 7

    What does this print? ```java public class Main { public static void main(String[] args) { Object o = 42; if (o instanceof Integer n) { System.out.println(n + 1); } else { System.out.println("no"); } } } ```

    1. A. 43Correct answer

      The boxed Integer 42 matches the type pattern and binds the variable to 42; the then-branch then evaluates one more than that value as int arithmetic, printing 43.

    2. B. 42

      42 is the value the pattern binds, but the branch prints one more than the bound value, not the value itself.

    3. C. no

      The else branch runs only when the pattern fails to match, but an Integer selector always matches an Integer type pattern.

    4. D. Compilation fails: n is not usable inside the if body

      The binding is in scope throughout the then-branch, exactly where the match is guaranteed to hold, so there is no scope error.

    Explanation

    A successful instanceof pattern binds its variable for the whole branch where the match is known to hold. Here the boxed Integer matches, so the binding takes the value 42, and the branch computes and prints one more than that. The lesson is to read what the branch actually does with the binding, not just the raw matched value.

  8. Question 8

    What does this print? ```java public class Main { record Point(int x, int y) {} static int f(Object o) { return switch (o) { case Point(int x, int y) -> x + y; default -> -1; }; } public static void main(String[] args) { System.out.println(f(new Point(2, 5))); } } ```

    1. A. -1

      -1 is the default result, but a Point always matches the record pattern Point(int x, int y).

    2. B. 7Correct answer

      The record pattern deconstructs the Point via its accessors, binding x=2 and y=5; the arm computes x + y as int addition, giving 7.

    3. C. 25

      25 is the string concatenation of the two digits, but x + y here is int arithmetic, not empty-string concatenation.

    4. D. Point[x=2, y=5]

      This is the record's generated toString() output, which this code never calls.

    Explanation

    A record pattern deconstructs the record by position, calling its accessors and binding the components as ordinary locals of their declared types. Because those components are ints, the arm performs integer addition rather than string concatenation, and the record's toString() is never involved.

Practise all 25 Pattern Matching (instanceof, switch, record patterns) questions

OCP Java SE 25 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 25

Other topics in this pack