Java Records

Introduction

Java 14 introduced a new feature called Records, which became a standard feature in Java 16. Records are a special kind of class in Java that are designed to model immutable data and eliminate boilerplate code associated with creating classes that are primarily used to store data. Records provide a concise way to declare data-carrying classes by automatically generating methods like equals(), hashCode(), toString(), and getters for all fields.

Key Points:

  • Conciseness: Records reduce boilerplate code by automatically generating common methods.
  • Immutability: Fields in records are implicitly final, promoting immutability.
  • Data-Centric: Records are intended for classes that primarily encapsulate data.
  • Easy Declaration: Simplified syntax for declaring data classes.

Declaring a Record

A record is defined using the record keyword, followed by the record name and a list of fields (also known as components) in parentheses.

Syntax

record RecordName(Type1 field1, Type2 field2, ...) {} 
  • RecordName: The name of the record.
  • field1, field2, …: The fields (components) of the record, with their respective types.

Example: Simple Record Declaration

public record Person(String name, int age) {} 
  • Concise Declaration: The record Person is declared with two fields, name and age, in a concise manner.
  • Automatic Method Generation: The record automatically provides implementations for equals(), hashCode(), toString(), and getter methods (name() and age()).

Benefits of Using Records

  • Reduced Boilerplate: Records eliminate the need to manually write common methods, such as getters, equals(), hashCode(), and toString().
  • Immutability: The fields of a record are implicitly final, ensuring that the data is immutable once set.
  • Improved Readability: Records provide a clear and concise way to declare data classes, improving code readability.

Generated Methods

When you define a record, Java automatically generates the following methods:

  1. Getters: For each field, a getter method is generated with the same name as the field.
  2. equals(): Compares two records for equality based on their fields.
  3. hashCode(): Generates a hash code based on the fields of the record.
  4. toString(): Returns a string representation of the record, including its fields and their values.
  5. canonical constructor: A constructor that takes parameters for all fields.

Example: Generated Methods for a Record

record Person(String name, int age) {} public class RecordExample { public static void main(String[] args) { Person person = new Person("Rahul", 25); // Using generated methods System.out.println("Name: " + person.name()); // Getter for name System.out.println("Age: " + person.age()); // Getter for age System.out.println("Hash Code: " + person.hashCode()); System.out.println("String Representation: " + person); } } 

Output:

Name: Rahul Age: 25 Hash Code: -1854581303 String Representation: Person[name=Rahul, age=25] 

Explanation:

  • Getters: The record provides getter methods name() and age() for accessing the fields.
  • hashCode(): The hash code is generated based on the fields of the record.
  • toString(): The string representation includes the record name and its fields with their values.

Customizing Records

While records automatically generate methods, you can still customize them if needed. For example, you can override the generated methods or add additional methods to a record.

Example: Customizing a Record

record Person(String name, int age) { // Additional method public String greeting() { return "Hello, my name is " + name + " and I am " + age + " years old."; } // Overriding toString() method @Override public String toString() { return "Person Record: { Name: " + name + ", Age: " + age + " }"; } } public class CustomRecordExample { public static void main(String[] args) { Person person = new Person("Ananya", 30); // Using the custom method System.out.println(person.greeting()); // Using the overridden toString() method System.out.println(person); } } 

Output:

Hello, my name is Ananya and I am 30 years old. Person Record: { Name: Ananya, Age: 30 } 

Explanation:

  • Additional Method: The greeting() method provides a custom behavior for the record.
  • Overriding toString(): The default toString() method is overridden to provide a custom string representation.

Restrictions on Records

  • Immutable Fields: Fields in a record are implicitly final, promoting immutability.
  • No Customization of Inheritance: Records implicitly extend java.lang.Record and cannot extend other classes.
  • Not Suitable for Complex Behavior: Records are primarily intended for data encapsulation and are not suited for classes with complex behavior.

Use Cases for Records

Use Case 1: Data Transfer Objects (DTOs)

Records provide a concise way to create classes that are primarily used to carry data. They automatically generate commonly used methods such as equals(), hashCode(), and toString(), as well as getter methods for each field. This makes records ideal for creating Data Transfer Objects (DTOs), which are simple objects used to transfer data between different layers of an application.

Let’s create a User record that represents a user with fields for id, firstName, lastName, and email.

Step 1: Define the User Record

public record User(int id, String firstName, String lastName, String email) { } 

Explanation:

  • Record Declaration: The User record is declared with four fields: id, firstName, lastName, and email.
  • Automatic Method Generation: The record automatically generates equals(), hashCode(), toString(), and getter methods for each field.
  • Immutability: All fields in the record are implicitly final, ensuring immutability.

Step 2: Using the User Record in an Application

Let’s demonstrate how to use the User record in a simple application that manages a list of users.

import java.util.ArrayList; import java.util.List; public class UserRecordExample { public static void main(String[] args) { // Create a list of users List<User> users = new ArrayList<>(); // Add users to the list users.add(new User(1, "Rahul", "Sharma", "rahul.sharma@example.com")); users.add(new User(2, "Ananya", "Mehta", "ananya.mehta@example.com")); users.add(new User(3, "Vikas", "Patel", "vikas.patel@example.com")); // Print user information for (User user : users) { System.out.println("ID: " + user.id()); System.out.println("First Name: " + user.firstName()); System.out.println("Last Name: " + user.lastName()); System.out.println("Email: " + user.email()); System.out.println("Full Name: " + user.firstName() + " " + user.lastName()); System.out.println(); } } } 

Output:

ID: 1 First Name: Rahul Last Name: Sharma Email: rahul.sharma@example.com Full Name: Rahul Sharma ID: 2 First Name: Ananya Last Name: Mehta Email: ananya.mehta@example.com Full Name: Ananya Mehta ID: 3 First Name: Vikas Last Name: Patel Email: vikas.patel@example.com Full Name: Vikas Patel 

Use Case 2: Immutable Value Objects

Records are well-suited for modeling immutable value objects that represent a specific value or concept.

record Point(int x, int y) {} public class PointExample { public static void main(String[] args) { Point point = new Point(5, 10); System.out.println("Point Coordinates: (" + point.x() + ", " + point.y() + ")"); } } 

Output:

Point Coordinates: (5, 10) 

Use Case 3: Reducing Boilerplate in Simple Classes

Records can be used to reduce boilerplate code in simple classes that primarily store data and require standard methods.

record Employee(String id, String name, double salary) {} public class EmployeeExample { public static void main(String[] args) { Employee employee = new Employee("E123", "Raj", 75000); System.out.println("Employee ID: " + employee.id()); System.out.println("Employee Name: " + employee.name()); System.out.println("Employee Salary: $" + employee.salary()); } } 

Output:

Employee ID: E123 Employee Name: Raj Employee Salary: $75000.0 

Conclusion

Java Record provides a concise and efficient way to define immutable data classes by automatically generating common methods. By reducing boilerplate code and promoting immutability, records enhance code readability and maintainability, making them an ideal choice for data-centric classes.

Summary:

  • Conciseness: Records reduce boilerplate code by automatically generating common methods.
  • Immutability: Fields in records are implicitly final, promoting immutability.
  • Data-Centric: Records are intended for classes that primarily encapsulate data.
  • Easy Declaration: Simplified syntax for declaring data classes.

By leveraging records, developers can write cleaner and more maintainable Java code, focusing on the essential aspects of data representation and manipulation.

Leave a Comment

Scroll to Top