What is the canonical constructor of a record, and how do the compact and explicit canonical forms differ?
answer
- canonical = params match components, in order
- compact: omit param list, no this.x = x (implicit assign after)
- compact body: validate + normalize parameters
- explicit canonical: full params, manual assignment
- other constructors must delegate via this(...)
basics
~20 sThe canonical constructor takes exactly the record's components, in order, and assigns each to its field. The compact form omits the parameter list and lets you validate or normalize the arguments; the fields are assigned automatically at the end. An explicit canonical constructor writes the parameter list and assignments yourself.
solid answer
~50 sEvery record has a **canonical constructor** whose parameters are exactly its components, in declared order. If you write nothing, the compiler supplies one that just assigns each argument to the matching field. You can customize it two ways. The **compact constructor** omits the parameter list entirely (`Point { ... }`) — inside it you validate or normalize the *parameters*, and the compiler implicitly assigns them to the fields **after** your code runs; you never write `this.x = x`. The **explicit canonical constructor** spells out the full parameter list and does the field assignments yourself, giving total control (but more boilerplate). The compact form is the idiomatic choice for validation (throwing on bad input), normalization (trimming a string, copying a list), or both. Both forms run for *every* construction, since all other constructors must delegate to the canonical one.
code
java · 12 linesrecord Temperature(double celsius) {
// compact constructor: validate + normalize
Temperature {
if (celsius < -273.15)
throw new IllegalArgumentException("below absolute zero");
celsius = Math.round(celsius * 100) / 100.0; // normalize the parameter
// implicit: this.celsius = celsius; (added by compiler after this block)
}
// non-canonical constructor must delegate to the canonical one
Temperature(int whole) { this((double) whole); }
}go deeper
Can write a compact constructor to validate input and knows the field assignment is implicit (no this.x = x).
Explains the difference between compact and explicit canonical forms, where normalization/defensive copying goes, and that non-canonical constructors must delegate via this(...).
Uses the canonical constructor as the single validated construction funnel, applies defensive copying for mutable components there, and reasons about invariants being guaranteed for every instance.
Establishes conventions for record validation/normalization, considers exception strategy and API contracts at construction boundaries, and reasons about how canonical-constructor invariants interact with deserialization (which also routes through it).
## What 'canonical' means A **constructor** is the special method that initializes a new object. The **canonical constructor** of a record is the one whose **parameters match the components exactly** — same types, same order. It is the single entry point through which all record instances are ultimately built. If you write no constructor at all, the compiler generates a default canonical constructor that simply does `this.x = x; this.y = y;` for each component. ## Form 1 — the compact constructor The **compact constructor** is a record-only convenience. You write the record name and a body, but **omit the parameter list**: ```java record Point(int x, int y) { Point { // no (int x, int y) here if (x < 0 || y < 0) throw new IllegalArgumentException("negative"); // no this.x = x; -- assignment is implicit, after this block } } ``` Key rules: - The parameters `x`, `y` are implicitly in scope (they have the component names). - You may **read and reassign** the parameters to normalize them (e.g. `name = name.trim();`). - After your block finishes, the compiler **implicitly assigns the (possibly modified) parameters to the fields**. You must not write the assignments yourself. This is the idiomatic place for **validation** (reject bad input) and **normalization / defensive copying** (`members = List.copyOf(members);`). ## Form 2 — the explicit canonical constructor You may instead write the full canonical constructor with the parameter list and do the assignments manually: ```java record Point(int x, int y) { Point(int x, int y) { // full parameter list if (x < 0 || y < 0) throw new IllegalArgumentException(); this.x = x; // you assign the fields yourself this.y = y; } } ``` This is more verbose and offers no advantage over the compact form for typical cases — prefer compact unless you have a specific reason. ## Non-canonical constructors You can add extra constructors with different parameter lists, but each **must delegate** to the canonical constructor via `this(...)`. This guarantees the canonical constructor (and thus all its validation) always runs: ```java record Point(int x, int y) { Point(int v) { this(v, v); } // delegates to canonical } ``` ## Why this design Funneling all construction through one validated entry point means a record can never exist in an invalid state — validation written once in the compact constructor protects every code path that builds the record.
- In a compact constructor, why must you NOT write this.x = x yourself?Because the compiler inserts the field assignments automatically at the end of the compact constructor, using the (possibly reassigned) parameter values. Writing the assignments yourself is illegal in the compact form — its whole point is to let you adjust the parameters and have the assignment happen implicitly afterward.
- If you add a second constructor with fewer arguments, what must it do?It must delegate to the canonical constructor with `this(...)` as its first statement, supplying values for every component. This ensures the canonical constructor's validation/normalization always runs, so no record can bypass it and end up invalid.
saying these in an interview costs you the question
- Writing this.x = x inside a compact constructor — assignment is implicit and explicit assignment is not allowed there.
- Thinking the compact constructor takes no parameters — the component parameters are implicitly in scope.
- Believing a non-canonical constructor can skip delegating to the canonical one.
- Assuming validation in one constructor doesn't apply to others — all construction funnels through the canonical constructor.
- Confusing the compact constructor with a no-arg constructor.