Skip to content

Records in depth: validation, defensive copies and withers - #106

Open
mperor wants to merge 1 commit into
masterfrom
DAILY/93-records-in-depth
Open

mperor wants to merge 1 commit into
masterfrom
DAILY/93-records-in-depth

Conversation

@mperor

@mperor mperor commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

What you'll learn

  • a compact canonical constructor can validate arguments and normalize them (e.g. trimming a String) before the fields are assigned
  • wrapping a mutable component with List.copyOf() in the compact constructor gives the record a real defensive copy and an immutable view, closing the classic "record holds a mutable list" leak
  • a "wither" method (withX) returns a new record instance with one component changed, leaving the original untouched — records have no setters, so this is the idiomatic way to get an "updated" value
  • pitfall: a record with an array component gets a generated equals()/hashCode() that compares the array by reference, not content, so two records holding "the same" array data can still be unequal

Why it matters

Records look like simple data holders, but the compact constructor is a real extension point for invariants, and the generated equals/hashCode/toString have sharp edges (arrays) that are easy to miss until a Set or Map behaves unexpectedly.

Read more

JEP 395: Records, Record Classes (Java Language Specification, §8.10)

Quiz

Given record Badge(String id, byte[] data) {}, if badge1 and badge2 are built with the same id and two different byte[] arrays holding identical bytes, what does badge1.equals(badge2) return?

Answer `false`. The compiler-generated `equals()` compares each component with `Objects.equals`/array identity semantics; for an array component that means reference equality (`Arrays.equals` is NOT used), so two distinct array instances are never equal even with identical contents — you'd need `Arrays.equals(badge1.data(), badge2.data())` to compare the bytes themselves.

Closes #93

@mperor mperor added the daily Queued for the daily learning PR label Oct 29, 2026
@github-actions

Copy link
Copy Markdown

Test Results

286 tests  +4   283 ✅ +4   5s ⏱️ -1s
 81 suites +1     3 💤 ±0 
 81 files   +1     0 ❌ ±0 

Results for commit 7d1ab98. ± Comparison against base commit b42879b.

This pull request skips 1 and un-skips 1 tests.
pl.mperor.lab.java.design.pattern.creational.singleton.SingletonTest ‑ testSerializationSafe()
pl.mperor.lab.java.design.pattern.creational.singleton.SingletonTest ‑ testThreadSafe()

@github-actions

Copy link
Copy Markdown

@mperor ⏰ This daily topic is still waiting for your review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

daily Queued for the daily learning PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Records in depth: validation, defensive copies and withers

2 participants