Mastery
Mastery/Java/A. Object model, identity & memory
T1 · high-leverage

Records: compact constructors, generated equals/hashCode/toString

A record auto-generates a canonical constructor, accessors (x(), not getX()), equals/hashCode (component-wise), and toString(). A compact constructor (no parameter list) lets you validate/normalize arguments before they're assigned to the (implicitly final) fields.

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

Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);

System.out.println("p1.equals(p2) [value equality]: " + p1.equals(p2));
System.out.println("p1 == p2 [reference identity]:  " + (p1 == p2));
System.out.println("toString():                     " + p1);
System.out.println("accessor (not getX()):           " + p1.x() + "," + p1.y());
java
record Positive(int v) {
    public Positive {
        if (v <= 0) throw new IllegalArgumentException("v must be positive, got " + v);
    }
}
try {
    new Positive(-3);
} catch (IllegalArgumentException e) {
    System.out.println("compact constructor validation fired: " + e.getMessage());
}

A real JLS rule this surfaces: the compact constructor's access modifier must exactly match the record's own declared access level -- not weaker, not stronger. A public record needs an explicit public on its compact constructor; leaving it off is a compile error, verified below (captured verbatim from a real javac run on a top-level public record PubRec(int v) { PubRec { ... } }):

PubRec.java:2: error: invalid canonical constructor in record PubRec
    PubRec {
    ^
  (attempting to assign stronger access privileges; was public)
1 error

Interview angle

A good "do you know modern Java" signal (records landed in Java 16). Interviewers sometimes push past "less boilerplate" to the real tradeoff: records give you a correct, free equals/hashCode/toString, but you give up mutable state and the ability to extend a class — articulating that tradeoff, not just the syntax, is what shows genuine understanding.

In the industry

Records rapidly became the default choice for DTOs, API request/response payloads, and immutable value objects in any codebase targeting Java 17+, directly replacing hand-written or Lombok-generated immutable POJOs for that use case. Teams still pinned to Java 8–11 continue reaching for Lombok's @Value to get the same effect, which is one of the more common reasons codebases on older LTS versions cite for wanting to upgrade.