Records in Java: Simplifying Data Classes
Records, introduced as a preview feature in Java 14 and officially released in version 16, are a powerful tool designed to simplify the creation of classes that primarily hold data. They significantly reduce boilerplate code by automatically generating commonly used methods like equals, hashCode, and toString. Ideal for creating immutable data carriers with minimal effort, records make your code more concise and easier to maintain.
Records are a new feature in Java (introduced in Java 14 as a preview-feature) that help reduce boilerplate code for classes that primarily hold data. They are ideal for creating immutable data carriers with minimal effort.
Why Use Records?
Before records, creating a simple data class required a lot of boilerplate code.
For example, consider a Favorites
class with two fields:
public class Favorites {
private final String name;
private final int rating;
public Favorites(String name, int rating) {
this.name = name;
this.rating = rating;
}
public String getName() { return name; }
public int getRating() { return rating; }
@Override
public String toString() {
return "Favorites[name=" + name + ", rating=" + rating + "]";
}
@Override
public boolean equals(Object o) { /*...*/ }
@Override
public int hashCode() { /*...*/ }
}
This is a lot of code for a simple data class. Before records, developers often used libraries like Lombok, which generates getters, setters, equals, and hashCode methods directly in the compiled class files, without adding boilerplate code to the source.
For example, with Lombok, the above class could be simplified to:
import lombok.Data;
@Data
public class Favorites {
private final String name;
private final int rating;
}
However, Lombok is an external dependency, and until records were introduced, Java did not have a built-in way to achieve this level of simplicity.
How Records Work
A record is a special kind of class that automatically generates:
- Private, final fields for each component.
- A public constructor (canonical constructor).
- Getter methods (without the
get
prefix). toString
,equals
, andhashCode
implementations.
Here’s how you can define the same Favorites
class as a record:
public record Favorites(String name, int rating) {}
That’s it! This single line replaces the entire Favorites
class above.
Example Usage
public class Main {
public static void main(String[] args) {
Favorites favorite = new Favorites("JBerries", 10);
System.out.println(favorite.name()); // Access fields directly
System.out.println(favorite.rating()); // Access fields directly
System.out.println(favorite); // Automatically uses toString()
}
}
Output:
JBerries
10
Favorites[name=JBerries, rating=10]
Key Features of Records
- Immutability: Records are immutable by default. Fields cannot be modified after creation.
- No Setters: Records do not generate setter methods.
- Compact Constructors: You can define a compact constructor for validation or additional logic.
public record Favorites(String name, int rating) {
public Favorites {
if (rating < 0 || rating > 5) {
throw new IllegalArgumentException("Rating must be between 0 and 5");
}
}
}
- Static Fields and Methods: You can add static fields and methods to records.
public record Favorites(String name, int rating) {
public static final String DEFAULT_NAME = "Unknown";
public static void printMessage() {
System.out.println("Hello from Favorites record!");
}
}
- Interfaces: Records can implement interfaces.
public record Favorites(String name, int rating) implements Runnable {
@Override
public void run() {
System.out.println(name + " is running with a rating of " + rating);
}
}
Limitations of Records
- No Inheritance: Records cannot extend other classes (they implicitly extend
java.lang.Record
). - No Instance Fields: All fields must be declared in the record header.
- Final by Default: Records are implicitly final and cannot be extended.
When to Use Records
Use records when:
- You need a simple, immutable data carrier.
- You want to avoid boilerplate code for
toString
,equals
, andhashCode
.
Avoid records when:
- You need mutable data.
- You require complex behavior or inheritance.
Records are a powerful addition to Java, making it easier to create data-centric classes. They eliminate boilerplate code and enforce immutability, making your code cleaner and more maintainable. Before records, developers relied on tools like Lombok to generate getters, setters, and other boilerplate code, but now Java provides a built-in solution.