2 · Model and validate · 25 MIN
Records and value-oriented modeling
Records make a small data carrier’s components explicit.
A record supplies accessors, equality, hashCode and a readable representation based on its components. A compact constructor can reject invalid values before an instance exists. This prevents every caller from having to remember the same basic check. Records are only shallowly immutable: a final reference to a mutable collection still permits that collection to change. Use defensive copies when a record must own a collection. Here the components are String and int, so there is no collection alias to manage.
Validate construction and copy mutable inputs at the ownership boundary.
Read the example
public class Main {
record Expense(String category, int amount) {
Expense {
if (category == null || category.isBlank() || amount < 0)
throw new IllegalArgumentException("Invalid expense");
}
}
public static void main(String[] args) {
var first = new Expense("food",5);
System.out.println(first.category());
System.out.println(first.equals(new Expense("food",5)));
}
}Check the expected output
food true
Your challenge
Create a Basket record containing a list of Expense values and preserve an immutable snapshot using List.copyOf.
Solution cost: O(1) construction for the scalar example; copying n items costs O(n). time · Account for collection storage separately from the returned result. space
Common trap
A record containing a mutable list is not deeply immutable automatically.
Study the project implementation
public class Main {
record Expense(String category, int amount) {
Expense {
if (category == null || category.isBlank() || amount < 0)
throw new IllegalArgumentException("Invalid expense");
}
}
public static void main(String[] args) {
var first = new Expense("food",5);
System.out.println(first.category());
System.out.println(first.equals(new Expense("food",5)));
}
}Further reading: Official documentation
Next lesson: Parsing and exception boundaries →