8.2. Record Classes (Java 14+)
Record classes are a special kind of class that act as transparent carriers for immutable data. They automatically generate constructors, accessors, equals()
, hashCode()
, and toString()
methods.
Basic Record Syntax
java
// Simple record declaration
record Point(int x, int y) { }
record Person(String name, int age, String email) { }
record Circle(double radius, String color) { }
Using Records
java
public class RecordDemo {
public static void main(String[] args) {
// Creating record instances
Point p1 = new Point(10, 20);
Point p2 = new Point(10, 20);
Point p3 = new Point(30, 40);
Person person = new Person("Alice", 30, "alice@example.com");
// Automatic accessors (no get prefix)
System.out.println("Point: (" + p1.x() + ", " + p1.y() + ")");
System.out.println("Person: " + person.name() + ", " + person.age());
// Automatic equals and hashCode
System.out.println("p1 equals p2: " + p1.equals(p2)); // true
System.out.println("p1 equals p3: " + p1.equals(p3)); // false
// Automatic toString
System.out.println(p1); // Point[x=10, y=20]
System.out.println(person); // Person[name=Alice, age=30, email=alice@example.com]
}
}
Customizing Records
java
record BankAccount(String accountNumber, String accountHolder, double balance) {
// Compact constructor for validation
public BankAccount {
if (balance < 0) {
throw new IllegalArgumentException("Balance cannot be negative");
}
if (accountNumber == null || accountNumber.isBlank()) {
throw new IllegalArgumentException("Account number is required");
}
}
// Additional methods
public BankAccount deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
return new BankAccount(accountNumber, accountHolder, balance + amount);
}
public BankAccount withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Withdrawal amount must be positive");
}
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
return new BankAccount(accountNumber, accountHolder, balance - amount);
}
// Static methods
public static BankAccount createEmpty(String accountNumber, String accountHolder) {
return new BankAccount(accountNumber, accountHolder, 0.0);
}
}
Record with Additional Fields
java
record Employee(String id, String name, double salary) {
// Additional field (must be static)
private static final String COMPANY = "Tech Corp";
// Additional computed field (not part of state)
public String email() {
return name.toLowerCase().replace(" ", ".") + "@" + COMPANY.toLowerCase() + ".com";
}
public String displayInfo() {
return String.format("%s (%s) - $%.2f - %s", name, id, salary, email());
}
}
Records in Collections
java
import java.util.*;
import java.util.stream.Collectors;
record Student(String id, String name, int grade) implements Comparable<Student> {
@Override
public int compareTo(Student other) {
return Integer.compare(this.grade, other.grade);
}
}
public class StudentRecords {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("S1", "Alice", 85),
new Student("S2", "Bob", 92),
new Student("S3", "Charlie", 78)
);
// Sort using natural ordering (by grade)
students.sort(Comparator.naturalOrder());
// Process records
students.forEach(System.out::println);
// Use in maps
Map<String, Student> studentMap = students.stream()
.collect(Collectors.toMap(Student::id, student -> student));
}
}
Record Limitations
- Implicitly final - cannot be extended
- Components are implicitly final
- Cannot declare instance fields outside the header
- All components become private final fields