Records practice questions

From OCP Java SE 17 (1Z0-829) · 16 questions on this topic

Records practice questions from OCP Java SE 17 (1Z0-829). This pack has 16 questions tagged Records, 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 Records

  1. Question 1

    Which statement about records is correct?

    1. A. Record components are mutable via generated setters

      Records never generate setters; components are immutable after construction and are exposed only through accessors, so there is no mutation path.

    2. B. A record is implicitly final and its components map to private final fieldsCorrect answer

      Correct: every record class is implicitly final, implicitly extends java.lang.Record, and stores each header component in a private final field with a public accessor and no setter.

    3. C. A record can extend another class

      Records cannot declare an extends clause; the superclass is always java.lang.Record, so a record cannot extend another class.

    4. D. A record may declare additional instance fields beyond its components

      Instance fields outside the record header are a compile-time error; only static fields may be added in the body.

    Explanation

    Every record class is implicitly final, implicitly extends java.lang.Record, and turns each header component into a private final field exposed by a public accessor with no setter. Records lock down instance state and inheritance while still permitting interfaces, static members, and extra methods, so the skill is separating what is restricted from what remains allowed.

  2. Question 2

    The record explicitly overrides its accessor. What does this print? ```java public class Main { record Tag(String name) { public String name() { return name.toUpperCase(); } } public static void main(String[] args) { Tag t = new Tag("beta"); System.out.println(t.name() + " " + t); } } ```

    1. A. Compilation fails: the accessor name() cannot be redeclared in the record body

      A record may hand-write an accessor; it only has to be public, return the component type, and take no parameters, so redeclaring name() is legal, not a compile error.

    2. B. BETA Tag[name=BETA]

      Assumes the generated toString calls the accessor methods, so the override would flow through; toString reads the component fields directly, so it shows beta, not BETA.

    3. C. BETA Tag[name=beta]Correct answer

      The override makes t.name() return BETA, but the generated toString reads the component field directly (still "beta"), so it renders Tag[name=beta] - the two deliberately disagree.

    4. D. beta Tag[name=beta]

      Assumes an explicit accessor in a record body is ignored; the override wins for direct calls, so t.name() returns BETA, not beta.

    Explanation

    Trace: a record may hand-write an accessor, and that declaration simply suppresses the implicitly generated one — so `t.name()` runs the override and yields `BETA`. But the generated `toString` (like the generated `equals` and `hashCode`) is defined in terms of the record's component FIELDS, not the accessor methods: it is linked through the `ObjectMethods` bootstrap with direct field getters. The field still holds the string passed to the canonical constructor, `"beta"`, so `toString` renders `Tag[name=beta]`. The two disagree, which is exactly the bug a value-changing accessor introduces. Why the others are wrong: `BETA Tag[name=BETA]` assumes the generated toString calls the accessor methods, so an overridden accessor would flow through to it. It reads the fields directly, so it does not. `beta Tag[name=beta]` assumes an explicit accessor in a record body is ignored (or illegal to call), leaving the generated one in place. The override wins for direct calls. `Compilation fails: the accessor name() cannot be redeclared...` assumes accessors are sealed off. They may be overridden — it only has to be `public`, return the component type, and take no parameters. Exam tip: overriding an accessor does NOT change equals/hashCode/toString — those are derived from the components' fields. Watch for the reverse trap too: an override that also narrows access, e.g. `String name()` without `public`, does not compile, because the implicit accessor is public.

  3. Question 3

    A record is declared inside a method body (a local record). Which statement is correct?

    1. A. Local records are not allowed; records must be top-level or nested in a class

      Local records are explicitly supported since Java 16, precisely for method-scoped data carriers.

    2. B. A local record is implicitly static and cannot directly access instance members of the enclosing classCorrect answer

      Correct: a record declared in a method body is implicitly static; having no enclosing instance, it cannot directly use the surrounding object's instance members, so data must arrive through its components.

    3. C. A local record may be declared non-static so it can capture the enclosing instance

      There is no way to make a local record inner; static is implicit and cannot be removed, so it cannot capture the enclosing instance.

    4. D. A local record can only be declared inside a lambda expression

      Local records work in any method body, whether or not a lambda is involved.

    Explanation

    Records may be declared inside a method body, and such local records, like local enums and local interfaces, are implicitly static. With no enclosing instance available, a local record cannot directly reference the surrounding object's instance fields or methods, so any state it needs must flow in through its components. Claims that it can capture the enclosing instance are therefore wrong.

  4. Question 4

    The compact constructor tries to clamp a negative value. What is the result? ```java public class Main { record Span(int lo, int hi) { Span { if (lo < 0) { this.lo = 0; } } } public static void main(String[] args) { System.out.println(new Span(-3, 7)); } } ```

    1. A. Compilation fails: a compact constructor cannot assign to this.loCorrect answer

      Correct: inside a compact constructor the component fields are still blank finals that the compiler assigns after the body runs, so writing this.lo is rejected with 'cannot assign a value to final variable lo'. Clamping must assign the parameter (lo = 0).

    2. B. Span[lo=-3, hi=7]

      Wrong: this assumes the assignment silently fails to stick and the original argument survives. It is a compile-time error, not a no-op, so no output is produced.

    3. C. Span[lo=0, hi=7]

      Wrong: this is what the parameter-assignment form (lo = 0) would produce, but this.lo = 0 never compiles, so the two forms are not interchangeable and the program is rejected.

    4. D. Compilation fails: a compact constructor must not contain an if statement

      Wrong: this invents a restriction. A compact constructor body is ordinary code that may branch, validate and normalize; it just may not assign the component fields.

    Explanation

    Trace: inside a compact constructor the component fields are still blank finals — the compiler assigns them, from the constructor's implicit parameters, only after the body has run. So the body may never write `this.lo`; javac reports `error: cannot assign a value to final variable lo`. The way to clamp is to assign the PARAMETER instead: `if (lo < 0) { lo = 0; }`. That mutated parameter is what the compiler then commits to the field, and the record would print `Span[lo=0, hi=7]`. Why the others are wrong: `Span[lo=0, hi=7]` is what the author intended and what the parameter-assignment version produces, but `this.lo = 0;` never compiles — the two forms are not interchangeable. `Span[lo=-3, hi=7]` assumes the assignment silently fails to stick (as if it wrote to a copy) and the original argument survives. It is a compile-time error, not a no-op. `Compilation fails: a compact constructor must not contain an if statement` invents a restriction. A compact constructor body is ordinary code — it may branch, validate, throw, and normalize; it just may not assign the fields, and it may not end with an explicit `return` value or a `this(...)` call. Exam tip: compact constructor ⇒ assign the PARAMETER (`lo = 0;`). Full canonical constructor ⇒ assign the FIELD (`this.lo = lo;`), and you must assign every one of them or the code will not compile.

  5. Question 5

    This record replaces the implicit canonical constructor with an explicit one. What is the result? ```java public class Main { public record Score(String player, int points) { private Score(String player, int points) { this.player = player; this.points = points; } } public static void main(String[] args) { System.out.println(new Score("ada", 10)); } } ```

    1. A. Compilation fails: the assignments this.player = player; are illegal because record fields are final

      A full canonical constructor is required to assign this.player and this.points itself, so those assignments are legal, not illegal; it is the compact form that forbids them.

    2. B. Score[player=ada, points=10]

      Assumes the canonical constructor's access modifier is free to choose as in an ordinary class; for a record it must be at least as accessible as the record, so private on a public record is rejected.

    3. C. Compilation fails: a record may declare a compact constructor but not a full canonical one

      A full canonical constructor with the exact component signature is perfectly legal; the compact form is not the only way to intervene, so the failure is the private access, not the full form.

    4. D. Compilation fails: the canonical constructor of a public record cannot be privateCorrect answer

      The canonical constructor is part of a record's public API, so on a public record it must be public; marking it private narrows access and javac rejects it (attempting to assign stronger access privileges).

    Explanation

    Trace: an explicitly declared canonical constructor must be at least as accessible as the record class itself, because the canonical constructor is part of the record's public API — it is what `new` and deserialization reach for. `Score` is declared `public`, so its canonical constructor must be `public` too. Marking it `private` narrows the access, and javac reports `error: invalid canonical constructor in record Score` with `(attempting to assign stronger access privileges; was public)`. Dropping `private` (or writing `public`) makes the class compile and print `Score[player=ada, points=10]`. Why the others are wrong: `Score[player=ada, points=10]` assumes the canonical constructor's access modifier is free to choose, as it would be in an ordinary class. For a record it is pinned by the record's own accessibility. `Compilation fails: a record may declare a compact constructor but not a full...` assumes the compact form is the only legal way to intervene. A full canonical constructor with the exact component signature is perfectly legal — it just has to be accessible enough, and it must assign every field. `Compilation fails: the assignments this.player = player; are illegal...` confuses the two forms. In a FULL canonical constructor you must assign `this.player` and `this.points` yourself; it is the COMPACT form that forbids those assignments and performs them implicitly at the end. Exam tip: for a `public record`, the explicit canonical constructor must be `public`; for a package-private record, package-private or wider is enough. Reverse trap: non-canonical constructors have no such rule — a `private` extra constructor that delegates with `this(...)` is fine.

  6. Question 6

    What does this print? ```java public class Main { record Point(int x, int y) {} public static void main(String[] args) { System.out.println(new Point(3, 4)); } } ```

    1. A. Point[x=3, y=4]Correct answer

      Correct: records generate toString from the type name and all components in header order using square brackets and name=value pairs separated by a comma and a space, producing Point[x=3, y=4].

    2. B. Point{x=3, y=4}

      Curly braces are not the record format; the generated form uses square brackets.

    3. C. Point(3, 4)

      This omits the component names; the generated form includes each name followed by = and its value.

    4. D. Compilation fails: toString is not defined

      toString is generated automatically for every record, so it compiles and never falls back to the hash-based form from Object.

    Explanation

    Printing an object invokes its toString, and a record generates one automatically. The generated form lists the type name followed by each component as name=value in header order, wrapped in square brackets and separated by a comma and a space. Answer choices typically differ only in bracket style, spacing, or whether the component names appear.

  7. Question 7

    What is the result of compiling and running this code? ```java public class Main { record Point(int x, int y) { int count = 0; } public static void main(String[] args) { System.out.println(new Point(1, 2)); } } ```

    1. A. Point[x=1, y=2]

      Assumes an extra instance field is merely ignored by the generated members; it is not tolerated at all, so the class does not compile.

    2. B. Compilation fails: a record body cannot declare an instance fieldCorrect answer

      A record's state is exactly its component list, so an added instance field like int count = 0 is rejected with "field declaration must be static" (static fields are still allowed).

    3. C. Point[x=1, y=2, count=0]

      Assumes the generated toString reports every field in the body; toString is derived only from the record components, and in any case this never compiles.

    4. D. Compilation fails: count must be initialized in a compact constructor

      Invents a rule about compact-constructor initialization; count is already initialized, and the failure is the mere existence of an instance field, not how it is initialized.

    Explanation

    Trace: a record's state is exactly its component list. The compiler generates one private final field per component and forbids any additional instance field in the body, so that the state description in the header is the whole truth. `int count = 0;` is an instance field declaration, so javac rejects it with `error: field declaration must be static` and the hint `(consider replacing field with record component)`. Static fields are still allowed — only instance fields are banned. Why the others are wrong: `Point[x=1, y=2]` assumes an extra instance field is merely ignored by the generated members; it is not tolerated at all — the class does not compile. `Point[x=1, y=2, count=0]` assumes the generated toString reports every field in the body; toString is derived from the record components only, and in any case this code never compiles. `Compilation fails: count must be initialized in a compact constructor` invents a rule: the field IS initialized here, and a compact constructor may not assign fields anyway. The failure is the existence of the instance field, not its initialization. Exam tip: a record may declare static fields, static initializers, static methods, instance methods and nested types in its body — but never an instance field. Reverse trap: `static int count = 0;` in the same body compiles fine.

  8. Question 8

    What does this print? ```java import java.util.*; public class Main { record Item(String sku, int qty) {} public static void main(String[] args) { List<Item> items = List.of(new Item("A1", 2), new Item("B2", 5)); System.out.println(items); } } ```

    1. A. [Item(sku=A1, qty=2), Item(sku=B2, qty=5)]

      Wrong: this uses round brackets around the components, echoing the constructor call. The generated record toString uses square brackets.

    2. B. [Item[A1, 2], Item[B2, 5]]

      Wrong: this drops the component names. The generated record toString always prefixes each value with its component name and =.

    3. C. [Main$Item@1b6d3586, Main$Item@4554617c]

      Wrong: this assumes the record inherits Object.toString (class name plus identity hash), as an ordinary class would. Records override toString for you.

    4. D. [Item[sku=A1, qty=2], Item[sku=B2, qty=5]]Correct answer

      Correct: the record's generated toString uses the exact form SimpleName[component=value, ...] with square brackets, and printing the List wraps those elements in its own brackets.

    Explanation

    Trace: printing a `List` calls `AbstractCollection.toString`, which wraps the elements in square brackets separated by `, ` and calls `toString` on each element. Each element is a record, and the record's implicitly generated `toString` uses the exact form `SimpleName[component=value, component=value]` — square brackets, `name=value` pairs, comma-and-space separators. So the list renders as `[Item[sku=A1, qty=2], Item[sku=B2, qty=5]]`. Why the others are wrong: `[Item(sku=A1, qty=2), Item(sku=B2, qty=5)]` uses round brackets around the components, echoing the constructor call. The generated format uses square brackets. `[Item[A1, 2], Item[B2, 5]]` drops the component names. The generated toString always prefixes each value with its component name and `=`. `[Main$Item@1b6d3586, Main$Item@4554617c]` assumes the record inherits `Object.toString` (class name plus identity hash), as an ordinary class would. Records override it for you. Exam tip: memorise the record toString shape exactly — the simple name, then `[`, then `component=value` pairs joined by `, `, then `]`. It is the record's SIMPLE name, so a nested record prints `Item[...]`, not `Main$Item[...]` or `Main.Item[...]`.

Practise all 16 Records questions

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

Open OCP Java SE 17

Other topics in this pack