Skip to content

Lesson 05 · Records

Objectives

After this lesson you will be able to:

  • Declare a record and explain what the compiler generates.
  • Write canonical and compact constructors and validate components.
  • Know the restrictions: implicit final, no extra instance fields, no extends.

What a record is

A record is a transparent carrier for immutable data. From the component list the compiler generates: a canonical constructor, a private final field and an accessor per component (named x(), not getX()), plus equals, hashCode, and toString.

java
record Point(int x, int y) { }

Point p = new Point(1, 2);
p.x();              // 1   — accessor, NOT getX()
p.equals(new Point(1, 2));   // true — value-based equals
p.toString();       // "Point[x=1, y=2]"

Exam trap

A record is implicitly final, its components are private final, and it cannot extends any class (it already extends java.lang.Record). It can implement interfaces. You cannot add extra instance fields — only the components are state (static fields are allowed).

Compact and canonical constructors

The compact constructor omits the parameter list and runs before the implicit field assignments — ideal for validation or normalization. You assign to parameters, not this.x.

java
record Range(int lo, int hi) {
    Range {                          // compact: no (int lo, int hi)
        if (lo > hi) throw new IllegalArgumentException("lo > hi");
        // fields lo, hi are assigned automatically AFTER this block
    }
}

You may instead write the canonical constructor in full (then you must assign every field), or add extra constructors that delegate via this(...):

java
record Range(int lo, int hi) {
    Range(int hi) { this(0, hi); }   // extra constructor delegates to canonical
}

Gotcha

In a compact constructor you assign normalized values to the parameter (lo = Math.min(...)), not this.lo. The compiler copies the (possibly reassigned) parameters into the final fields after the block. Writing this.lo = ... there is a compile error.

Records as value types

Because equals/hashCode are component-based, records are perfect map keys and DTOs. They also work directly with record patterns (Lesson 08) for deconstruction.

SDET note

Records make excellent test fixtures and expected-value objects: value equality means assertEquals(expected, actual) "just works" without a hand-written equals. Prefer a record over a class with mutable getters/setters for test data.

Key Takeaways

  • A record auto-generates the canonical constructor, accessors (x()), and value-based equals/hashCode/toString from its components.
  • It is implicitly final, components are private final, no extends, no extra instance fields — but it can implement interfaces and have static members.
  • The compact constructor validates/normalizes by assigning to parameters; fields are set automatically afterward.
  • Records are immutable value carriers — ideal for keys, DTOs, and record-pattern deconstruction.

Lesson Quiz

Lesson Quiz · Records0 / 5
  1. How do you read component x of record Point(int x, int y)?

    • Ap.getX()
    • Bp.x
    • Cp.x()
    • Dp.get("x")
  2. Which is ILLEGAL for a record?

    • Aimplements Comparable
    • Bextends ArrayList
    • Ca static field
    • Da compact constructor
  3. In a compact constructor, how do you normalize lo?

    record Range(int lo, int hi) {
        Range { /* here */ }
    }
    • Athis.lo = Math.max(0, lo);
    • Blo = Math.max(0, lo);
    • CRange.lo = ...;
    • Dreturn Math.max(0, lo);
  4. What does new Point(1,2).equals(new Point(1,2)) return?

    • Afalse (different objects)
    • Btrue (value-based equals)
    • CCompile error
    • DNullPointerException
  5. Can a record declare an extra instance field beyond its components?

    • AYes
    • BNo
    • COnly if final
    • DOnly if static

Next: Sealed Types. Run the matching code in labs/src/main/java/com/jse21/m03_oop/.