Java 11: Records & Value Semantics (Java 16)
easy⏱ 5 mincoursejava
What a record gives you for free
record User(String name, int age) {} auto-generates: (1) private final String name; private final int age;, (2) a canonical constructor, (3) accessors name() and age() — note: no get prefix, (4) equals/hashCode from the component tuple, (5) toString as User[name=..., age=...], (6) implicitly final — you can't extend a record.
public record User(String name, int age) {}
// Auto-generated equivalents:
// name() -> String
// age() -> int
// equals, hashCode by (name, age) tuple
// toString -> "User[name=alice, age=30]"
Compact constructor for validation
The compact constructor has no parameter list — the canonical parameters are already in scope. Use it to validate or normalize. The compiler then assigns to the fields from the (possibly modified) parameters. You can't reference this.name = ... in a compact constructor — you assign to the implicit parameter.
public record Price(String currency, long cents) {
public Price { // compact — no param list
if (cents < 0) throw new IllegalArgumentException("negative");
currency = currency.toUpperCase(); // normalize
}
}
Derived methods & static factories
Records are classes — you can add methods, static factories, even nested types. What you cannot do: add instance fields (state must come from the components) or extend another class (records are implicitly final and extend Record).
public record Money(long cents) {
public static Money usd(double dollars) { return new Money(Math.round(dollars * 100)); }
public Money plus(Money other) { return new Money(cents + other.cents); }
public double asDollars() { return cents / 100.0; }
}
Build a validated Price record
Create a Price class mirroring a record: fields currency and cents, a constructor that uppercases currency and rejects negative cents, and a plus method. Use a HashSet / Map to prove that two Price objects with identical components are equal and share a hash code. Add a third Price with different cents to show they're not equal.
Records are for data, not behavior
If your class has behavior beyond computing from its components — state transitions, side effects, external calls — a record is probably the wrong shape. Use a record for DTOs, value objects, message payloads, API responses, keys in maps. Use a class for services, aggregates, and anything with identity independent of its fields.
Quiz: Records and equals
What defines record equality? The component tuple. new User("a", 1).equals(new User("a", 1)) is true even though they're different objects. This makes records ideal as map keys — but only if all components are themselves value-like (primitives, Strings, immutable objects).