Passing immutable data between objects is one of the most common yet mundane tasks in Java applications. Before Java 14, this often required creating classes with fields and methods that were prone to errors and obscured the class’s primary purpose. Java Records, introduced in Java 14 and fully adopted in Java 16, provide a more concise and expressive way to handle immutable data classes.
In this article, we’ll explore the fundamentals of records, their purpose, generated methods and some customization techniques.
The Purpose of Java Records
In many Java applications, we often create classes to hold simple data, like database results, query results, or information from external services. These data classes typically need to be immutable to ensure data integrity without requiring complex synchronization.
To achieve this, a traditional Java data class would include:
- Private, final fields for each piece of data.
- Getter methods for each field.
- A public constructor that takes arguments for each field.
- An
equals
method to compare objects based on field values. - A
hashCode
method that returns consistent values for matching field values. - A
toString
method that provides a readable representation of the class.
For example, here’s a basic Product
class implemented in the traditional way:
import java.util.Objects;
public class Product {
private final String name;
private final double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof Product)) {
return false;
} else {
Product other = (Product) obj;
return Objects.equals(name, other.name) && Double.compare(price, other.price) == 0;
}
}
@Override
public String toString() {
return "Product [name=" + name + ", price=" + price + "]";
}
// standard getters
}
Drawbacks of the Traditional Approach:
- Boilerplate Code: Repeating the same pattern for each data class, writing constructors, getters,
equals
,hashCode
, andtoString
methods. - Obscured Intent: The primary purpose of the class—holding data—is lost amidst the boilerplate code.
Java Records address these issues by explicitly declaring a class as a data class, removing the boilerplate and focusing on the data itself.
The Basics of Java Records
As of JDK 14, records allow us to replace repetitive data classes with a more compact and readable syntax. A record automatically provides implementations for equals
, hashCode
, toString
, and a constructor, all based on the fields you declare.
To create a Product
record, you would write:
public record Product(String name, double price) {}
This single line achieves the same functionality as the previous example but in a much more concise form.
Constructor in Records
With records, Java automatically generates a public constructor that accepts all the fields as arguments:
public Product(String name, double price) {
this.name = name;
this.price = price;
}
You can use this constructor to create instances just as you would with a traditional class:
Product product = new Product("Laptop", 999.99);
System.out.println(product); // Output: Product[name=Laptop, price=999.99]
Getters in Records
Records provide public getter methods for each field, named after the fields themselves:
String name = product.name(); // Returns "Laptop"
double price = product.price(); // Returns 999.99
Automatically Generated Methods
Records come with automatically generated methods for equals
, hashCode
, and toString
, saving you from writing these methods manually.
equals
The equals
method compares two record instances and returns true
if they have the same type and their field values match:
Product product1 = new Product("Laptop", 999.99);
Product product2 = new Product("Laptop", 999.99);
System.out.println(product1.equals(product2)); // Output: true
hashCode
The hashCode
method returns the same hash value for two record instances if all their field values match:
System.out.println(product1.hashCode() == product2.hashCode()); // Output: true
toString
The toString
method provides a string representation of the record, showing the class name and field values:
System.out.println(product); // Output: Product[name=Laptop, price=999.99]
Customizing Java Records
While records provide much functionality automatically, they also allow for customization, such as adding validation logic or defining additional methods.
Custom Constructors
Records support custom constructors where you can add validation logic. For instance, to ensure that the name
is not null
and price
is positive:
public record Product(String name, double price) {
public Product {
Objects.requireNonNull(name, "Name cannot be null");
if (price < 0) {
throw new IllegalArgumentException("Price cannot be negative");
}
}
}
You can also define additional constructors with different argument lists:
public record Product(String name, double price) {
public Product(String name) {
this(name, 0.0);
}
}
Static Methods and Fields
Records can include static variables and methods just like regular classes:
public record Product(String name, double price) {
public static final String DEFAULT_NAME = "Unknown Product";
public static Product defaultProduct() {
return new Product(DEFAULT_NAME, 0.0);
}
}
public class Main {
public static void main(String[] args) {
Product defaultProduct = Product.defaultProduct();
System.out.println(defaultProduct); // Output: Product[name=Unknown Product, price=0.0]
}
}
Java Records simplify the creation of immutable data classes by eliminating boilerplate code and focusing on the data. By using records, you gain automatic implementations of common methods like equals
, hashCode
, and toString
, making your code cleaner, more readable, and less error-prone.
If you’re working with data-centric classes in Java, consider using records to streamline your development process and improve the clarity and maintainability of your code. They offer a modern approach to handling immutable data, making Java applications more concise and expressive.
Stay tuned for more new updates regarding newer Java versions, don’t forget to subscribe to receive all new articles and Java tips !
Leave a Reply