Records practice questions

From OCP Java SE 21 (1Z0-830) · 17 questions on this topic

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

    What does this print? ```java public class Main { record Temp(double celsius) { double fahrenheit() { return celsius * 9 / 5 + 32; } } public static void main(String[] args) { System.out.println(new Temp(100).fahrenheit()); } } ```

    1. A. 100.0

      This value would mean the Celsius-to-Fahrenheit conversion never ran; the method does compute a result.

    2. B. Compilation fails: records cannot declare methods

      Records may declare extra instance methods freely; it is extra instance fields that are forbidden, so this compiles.

    3. C. 180.0

      This stops after the multiply and divide and forgets to add 32.

    4. D. 212.0Correct answer

      celsius is a double holding 100.0; evaluating left to right gives 100.0 * 9 = 900.0, / 5 = 180.0, + 32 = 212.0, and a double prints as 212.0.

    Explanation

    A record may declare its own instance methods, so the conversion method is legal. The component type drives the arithmetic: because the component is a double, the division does not truncate, and evaluating the expression left to right yields 212.0, which prints with a trailing .0.

  2. Question 2

    Which two statements about record class declarations are correct? (Choose two.)

    1. A. A record body may declare static fields and static methods, but not additional instance fields.Correct answer

      Static members are exempt from the state restriction: a record body may freely declare static fields and static methods (counters, factories, constants). Only additional instance fields are forbidden, because a record's instance state is exactly its component list.

    2. B. Declaring an explicit canonical constructor suppresses the generated accessors, which must then be written by hand.

      Declaring an explicit canonical constructor suppresses only the constructor; the accessors, equals, hashCode and toString are still generated. Each implicit member is suppressed only by declaring that exact member, so this conflates independent generation rules.

    3. C. A record may extend an abstract class, provided that class declares a no-argument constructor.

      A record implicitly extends java.lang.Record, and since Java is single-inheritance the record grammar has no extends clause at all; the no-argument-constructor proviso is a red herring borrowed from ordinary class inheritance. A record may still implement interfaces.

    4. D. A record class may be generic, so `record Pair<A, B>(A first, B second) {}` is a valid declaration.Correct answer

      Records are ordinary generic-capable classes: the header may carry type parameters, so record Pair<A, B>(A first, B second) {} compiles and behaves as expected.

    Explanation

    A record's instance state is exactly its component list, and nothing may be added to it — but that restriction applies only to INSTANCE fields. Static fields and static methods (counters, factories, constants) are unrestricted, which is why `A record body may declare static fields and static methods, but not additional instance fields.` is correct. Records are also ordinary generic-capable classes: the header may carry type parameters, so `record Pair<A, B>(A first, B second) {}` compiles and behaves as you would expect, making `A record class may be generic...` the second correct statement. Why the others are wrong: `A record may extend an abstract class, provided that class declares a no-argument constructor.` encodes the belief that a record is a normal class with a shorthand header, so ordinary inheritance rules apply. Every record implicitly extends `java.lang.Record`, and since Java is single-inheritance the record declaration grammar has no `extends` clause at all — the no-arg-constructor proviso is a red herring borrowed from ordinary class inheritance. (A record may still IMPLEMENT interfaces.) `Declaring an explicit canonical constructor suppresses the generated accessors, which must then be written by hand.` confuses two independent generation rules. Writing a constructor only suppresses the generation of the constructor; the accessors, `equals`, `hashCode` and `toString` are still generated. Each implicit member is suppressed only by explicitly declaring THAT member. Exam tip: keep the four generation rules separate — component fields, accessors, canonical constructor, and the trio of `equals`/`hashCode`/`toString` are each generated unless you declare that exact member yourself. And remember the asymmetry on membership: `extends` never, `implements` freely, `static` members yes, instance fields no.

  3. Question 3

    What is printed when the following program is run? ```java public class Main { record Range(int lo, int hi) { Range { if (lo > hi) { int tmp = lo; lo = hi; hi = tmp; } } } public static void main(String[] args) { Range r = new Range(10, 3); System.out.println(r.lo() + " " + r.hi()); } } ```

    1. A. 10 3

      This would be the result if the compact constructor body had no effect on the eventual field values. The body does reassign the parameters, and those reassigned values are used for the implicit field initialisation that follows the body.

    2. B. 3 10Correct answer

      In a compact canonical constructor, the formal parameters (lo and hi here) may be reassigned within the body. When the body finishes, the fields are implicitly initialised from the final parameter values. Because 10 > 3, the swap runs, leaving lo=3 and hi=10; r.lo() and r.hi() return those values (JLS §8.10.4.2).

    3. C. Compilation fails

      Reassigning the formal parameters inside a compact canonical constructor is explicitly permitted by JLS §8.10.4.2. The code is valid and compiles without error.

    4. D. A `NullPointerException` is thrown at runtime

      Every variable in the program is a primitive int; there are no references that could be null. The program runs to completion and prints the swapped values.

    Explanation

    A compact canonical constructor is declared without a parameter list; the record components serve as its implicit formal parameters, and those parameters may be reassigned within the body (JLS §8.10.4.2). When the body completes, the record's fields are implicitly initialised from the parameters' final values — not from their original call-site values. With arguments 10 and 3, the swap branch fires and the parameters become lo=3, hi=10 before the implicit field-assignment step. Assuming the body cannot influence field values produces the unswapped output; believing parameter reassignment is illegal produces the compile-error guess; all variables are primitive ints, so a NullPointerException cannot arise.

  4. Question 4

    The record below uses a compact constructor to clamp a negative amount to zero. What is the result? ```java public class Main { record Money(String currency, int cents) { Money { if (cents < 0) { this.cents = 0; } } } public static void main(String[] args) { System.out.println(new Money("USD", -5)); } } ```

    1. A. Compilation fails: a compact constructor may not assign to this.cents.Correct answer

      In a compact constructor the names are the implicit parameters and the fields are still definitely-unassigned blanks the compiler owns, so writing this.cents is illegal: javac reports cannot assign a value to final variable cents. The fix is cents = 0;.

    2. B. Compiles, then throws IllegalStateException: a record component cannot be reassigned.

      Moves the enforcement to runtime. The prohibition is entirely compile-time; nothing is thrown.

    3. C. Money[currency=USD, cents=-5]

      Believes the compact constructor runs too early to influence the fields, so the write is discarded and the raw argument stored. Wrong twice over: the write is a compile error, and a legal write to the parameter would in fact stick.

    4. D. Money[currency=USD, cents=0]

      What the author intended, and what cents = 0; would give, but not this.cents = 0;. It assumes a compact constructor addresses the fields directly, as an ordinary constructor does.

    Explanation

    Trace: inside a compact constructor the names `currency` and `cents` are the constructor's implicit PARAMETERS, not the fields. The body's job is to validate or normalise those parameters; when it completes normally the compiler emits the field assignments for you, copying each parameter into its field. Because the fields are still definitely-unassigned blanks that the compiler owns, writing to `this.cents` is illegal, and javac rejects the class with `error: cannot assign a value to final variable cents`. The fix is to drop `this.` and assign the parameter — `cents = 0;` — which the implicit trailing assignment then copies into the field. Why the others are wrong: `Money[currency=USD, cents=0]` is what the author intended and what you get from `cents = 0;`, but not from `this.cents = 0;`. It assumes a compact constructor addresses the fields directly, as an ordinary constructor does. `Money[currency=USD, cents=-5]` encodes the belief that the compact constructor runs too early to influence the fields, so any write inside it is discarded and the raw argument is stored. That is wrong twice over — the write is a compile error, and a legal write to the parameter would in fact stick. `Compiles, then throws IllegalStateException: a record component cannot be reassigned.` moves the enforcement to runtime. The prohibition is entirely a compile-time one; nothing is thrown. Exam tip: compact constructor — assign the PARAMETER (`cents = 0;`), never `this.cents`. Full-form canonical constructor — the exact reverse: you must assign `this.cents` yourself, and forgetting a field is `variable cents might not have been initialized`. Exam stems love to swap the two forms' rules. (Also note that in Java 21 a non-canonical record constructor must still have `this(...)` as its very first statement — the relaxation of that rule arrived later than this exam's version.)

  5. Question 5

    The `Person` record uses a compact canonical constructor to normalise its `name` component before it is stored. What is the output of the following program? ```java public class Main { record Person(String name, int age) { Person { name = name.strip(); } } public static void main(String[] args) { Person p = new Person(" Alice ", 30); System.out.println(p.name() + ":" + p.age()); } } ```

    1. A. Alice:30Correct answer

      Inside a compact canonical constructor each component is available as a mutable local parameter variable. Assigning `name = name.strip()` updates that local; when the body returns normally the compiler automatically appends `this.name = name` and `this.age = age`, storing `"Alice"`. The generated accessor `name()` returns `this.name`, so `p.name()` is `"Alice"` and `p.age()` is `30` (JLS §8.10.4).

    2. B. ` Alice :30`

      Assumes the compact constructor body cannot affect what is stored — as if `name` inside the constructor were already the immutable final field. In reality the component parameters are mutable local variables; `name = name.strip()` reassigns that local, and the compiler-generated suffix `this.name = name` (appended after the body) stores the stripped value, not the original.

    3. C. Compilation fails

      Assigning to a component parameter inside a compact canonical constructor body is entirely legal — it is the idiomatic way to validate or normalise a value before it is stored. What a compact constructor may not do is explicitly assign to the final field with `this.name = ...`; reassigning the mutable parameter variable is a distinct, permitted operation.

    4. D. `null:30`

      `String.strip()` returns a non-null `String` with leading and trailing whitespace removed; it never returns `null`. Storing `null` in `name` would require writing `name = null` explicitly, which is absent here. There is no code path in this constructor that could produce a `null` component.

    Explanation

    A compact canonical constructor exposes each record component as a mutable local parameter variable; the final fields are assigned automatically from those variables after the body returns normally, so any reassignment inside the body — such as calling `strip()` — is captured in the stored component (JLS §8.10.4). Treating the parameter as if it were already the immutable field, or believing the reassignment syntax is illegal, leads to wrong predictions; the constructor is valid and the normalised value persists. Record accessor methods take the component name directly with no `get` prefix (JLS §8.10.3), so `p.name()` and `p.age()` correctly retrieve the stored values.

  6. Question 6

    Which statement about record constructors is correct?

    1. A. A compact constructor may assign this.x directly to skip the implicit field assignments

      Assigning a component field inside a compact constructor is a compile-time error; the implicit assignments always run after the body.

    2. B. A record may declare additional non-canonical constructors, and each must delegate to another constructor via this(...) as its first statementCorrect answer

      A record may declare overloaded non-canonical constructors, but each must begin with an explicit this(...) call, funneling all construction through the canonical constructor.

    3. C. An explicit canonical constructor may leave some component fields unassigned

      An explicit non-compact canonical constructor must definitely assign every component field, or compilation fails.

    4. D. A compact constructor must declare the same parameter list as the record header

      The compact form omits the parameter list entirely; writing the parameters makes it a normal canonical constructor with the usual obligations.

    Explanation

    Every construction path in a record must funnel through the canonical constructor, which is the single place components are initialized and validated. A record may add overloaded constructors, but each non-canonical one must delegate with this(...) as its first statement. The canonical constructor itself must assign every component field, and the compact form cannot make those assignments directly because they are inserted automatically after its body.

  7. Question 7

    A record has both a compact constructor that normalises a component and an extra (non-canonical) constructor that delegates to the canonical one. What is printed? ```java public class Main { record Money(String currency, int cents) { Money { currency = currency.toUpperCase(); } Money(int cents) { this("usd", cents); } } public static void main(String[] args) { Money a = new Money(250); Money b = new Money("USD", 250); System.out.println(a.equals(b) + " " + a); } } ```

    1. A. true Money[currency=USD, cents=250]Correct answer

      Constructing through the extra constructor delegates to the canonical one and runs the compact body, which upper-cases the currency before it reaches the field; equals compares components so the two instances are equal, and toString shows named components.

    2. B. false Money[currency=usd, cents=250]

      Assumes the compact constructor's normalization is lost; its assignment to the parameter is what is committed to the field, so the currency becomes upper-case and both instances compare equal.

    3. C. Compilation fails

      A non-canonical constructor is legal as long as it delegates to another constructor, which it does here.

    4. D. true Money[USD, 250]

      Uses an abbreviated toString; the implicit record toString includes each component's name and separates entries with ", ".

    Explanation

    A non-canonical record constructor must delegate to another constructor, so a value built through it still passes through the canonical constructor and any compact-constructor normalization, which commits its adjusted parameter to the field. The implicitly generated equals compares components for equality, and the implicit toString lists each component by name.

  8. Question 8

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

    1. A. It is implicitly static, so its methods cannot access local variables of the enclosing methodCorrect answer

      JEP 395 allows records inside method bodies, and a local record is implicitly static, so its methods cannot use the enclosing method's local variables or the enclosing instance's state.

    2. B. It can capture effectively final local variables, like an anonymous class

      Capture is an inner-class/lambda mechanism; the implicit static-ness of a local record removes it.

    3. C. Local records are not permitted; a record must be top-level or nested in a class

      Local records are expressly legal; modeling intermediate values inside a method was a headline motivation of JEP 395.

    4. D. It becomes a member of the enclosing class, visible to the class's other methods

      A local type is scoped to the block that declares it, so other methods of the class cannot see it.

    Explanation

    Records may be declared locally inside a method body, and such a local record is implicitly static. Being static means it has no link to the enclosing instance or to the method's local variables, so it cannot capture them the way a local or anonymous class can. Its scope is limited to the block that declares it.

Practise all 17 Records questions

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

Open OCP Java SE 21

Other topics in this pack