Lesson Completion
Back to course

Records: Immutable Data Carriers

Beginner
12 minutes4.5Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Records (Java 14+) are data carrier classes—compact syntax for immutable data. record Person(String name, int age) auto-generates: constructor, getters, equals(), hashCode(), toString(). Immutable (all fields final).
  • Restrictions: Implicitly final, extends Record, can't extend other classes (can implement interfaces).
  • Use for: DTOs, value objects, API responses.
  • Replaces: Boilerplate POJOs (100 lines → 1 line!).
  • Compact constructor: Custom validation.
  • Benefits: Less code, readability, immutability guaranteed!

Think of shipping label. Traditional class = writing all info by hand (sender, receiver, weight, tracking#, date—tedious!). Record = pre-printed label template (just fill blanks, everything else automatic). Same info, 90% less work!


2. Conceptual Clarity (The "Simple" Tier)

💡 The Analogy

  • Traditional Class: Building house from scratch (foundation, walls, roof, plumbing)
  • Record: Prefab house (arrives complete, just place it)
  • Components: The blueprints (what data record holds)

3. Technical Mastery (The "Deep Dive)

Record Auto-Generated Members

MemberGenerated Automatically
ConstructorCanonical constructor with all components
Gettersname(), age() (not getName()!)
equals()Compares all components
hashCode()Based on all components
toString()Pretty print: Person[name=Alice, age=30]

4. Interactive & Applied Code

java
// ❌ BEFORE RECORDS: Traditional POJO (50+ lines!) class PersonOld { private final String name; private final int age; public PersonOld(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PersonOld)) return false; PersonOld person = (PersonOld) o; return age == person.age && name.equals(person.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "PersonOld{name='" + name + "', age=" + age + '}'; } } // ✅ AFTER RECORDS: 1 line! record Person(String name, int age) {} public class RecordsDemo { public static void main(String[] args) { demonstrateBasics(); demonstrateValidation(); demonstrateCustomMethods(); demonstrateNested(); } // Basic record usage static void demonstrateBasics() { System.out.println("=== BASIC RECORDS ==="); // Create record instance Person alice = new Person("Alice", 30); // Auto-generated getters (no 'get' prefix!) System.out.println("Name: " + alice.name()); System.out.println("Age: " + alice.age()); // Auto-generated toString System.out.println(alice); // Person[name=Alice, age=30] // Auto-generated equals/hashCode Person alice2 = new Person("Alice", 30); System.out.println("Equal? " + alice.equals(alice2)); // true // Immutable (no setters!) // alice.name = "Bob"; // ❌ COMPILE ERROR: final field } // Compact constructor (validation) record ValidatedPerson(String name, int age) { // Compact constructor (no parameter list!) public ValidatedPerson { if (name == null || name.isBlank()) { throw new IllegalArgumentException("Name required"); } if (age < 0 || age > 150) { throw new IllegalArgumentException("Invalid age"); } // Auto-assignment happens after this block } } static void demonstrateValidation() { System.out.println("\n=== VALIDATION ==="); try { var person = new ValidatedPerson("", 25); } catch (IllegalArgumentException e) { System.out.println("Error: " + e.getMessage()); } var valid = new ValidatedPerson("Bob", 25); System.out.println(valid); } // Custom methods record Employee(String name, int salary, String dept) { // Custom method public boolean isHighEarner() { return salary > 100000; } // Override accessor @Override public String name() { return name.toUpperCase(); } // Static method public static Employee createIntern(String name) { return new Employee(name, 50000, "Internship"); } } static void demonstrateCustomMethods() { System.out.println("\n=== CUSTOM METHODS ==="); var emp = new Employee("alice", 120000, "Engineering"); System.out.println("Name: " + emp.name()); // ALICE (overridden!) System.out.println("High earner? " + emp.isHighEarner()); var intern = Employee.createIntern("Bob"); System.out.println(intern); } // Nested records record Address(String street, String city) {} record PersonWithAddress(String name, Address address) {} static void demonstrateNested() { System.out.println("\n=== NESTED RECORDS ==="); var address = new Address("Main St", "NYC"); var person = new PersonWithAddress("Alice", address); System.out.println(person.name()); System.out.println(person.address().city()); } } // Real-world example: API Response record ApiResponse<T>(int status, String message, T data) {} class ApiDemo { public static void main(String[] args) { var response = new ApiResponse<>( 200, "Success", List.of(new Person("Alice", 30), new Person("Bob", 25)) ); System.out.println("Status: " + response.status()); System.out.println("Data: " + response.data()); } } // Records can implement interfaces interface Identifiable { String getId(); } record User(String id, String name) implements Identifiable { @Override public String getId() { return id; } }

5. The Comparison & Decision Layer

Traditional ClassRecordUse When
MutableImmutableNeed immutability
50+ lines1 lineData carriers
Can extend classesCannot extendDTOs, value objects
Custom getters/settersAuto-generatedSimple data

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "Why don't record getters follow JavaBean convention (getName() vs name())?" Answer: Records emphasize they're data carriers, not beans!

java
record Person(String name, int age) {} // ✅ Record style: name() person.name(); // Direct, concise // ❌ NOT: getName() (JavaBean style) // Records aren't beans—no setters, properties are components! // Benefit: Shorter, clearer they're immutable value objects

Pro-Tips:

  1. Compact vs Canonical constructor:
java
// Compact (validation) record Point(int x, int y) { public Point { // No params! if (x < 0 || y < 0) throw new IllegalArgumentException(); // Implicit: this.x = x; this.y = y; } } // Canonical (transformation) record Point(int x, int y) { public Point(int x, int y) { // With params this.x = Math.max(0, x); // Transform this.y = Math.max(0, y); } }
  1. Records vs classes:
java
// ✅ USE RECORD: Immutable data record Point(int x, int y) {} // ❌ USE CLASS: Mutable, complex behavior class BankAccount { private double balance; public void deposit(double amount) { ... } }

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01