When it comes to writing Java code, we often find ourselves creating simple data classes to encapsulate data and keep our code organized. These classes typically consist of private fields, getters, setters, constructors, and maybe some utility methods. However, maintaining these boilerplate pieces of code can be quite cumbersome and lead to code duplication. To address this issue, Java 14 introduced Record Classes, a concise and powerful feature that simplifies the creation of data classes. In this article, we will explore what Java Record Classes are, how they work, and how they can make your code cleaner and more maintainable.
What are Java Record Classes?
A Java Record Class is a new addition to the Java language introduced as a preview feature in Java 14 and later made a standard feature in Java 16. Record Classes are specifically designed to model data with minimal boilerplate code. They encapsulate immutable data and provide built-in implementations for equals(), hashCode(), and toString() methods. Record Classes are intended for simple data-holding objects, similar to what you would create with traditional data classes.
Syntax of a Java Record Class
The syntax to define a Java Record Class is straightforward:
public record Person(String name, int age) {
// Additional members, if needed
}
In this example, we’ve defined a simple Person record class with two properties: name and age. The keyword record is used to declare the record class, followed by the class name (Person in this case), and the list of components (properties) within parentheses.
Default Implementations
One of the major benefits of Java Record Classes is that they provide default implementations for common methods. These default implementations include:
The equals(Object other) Method
Compares two record objects for structural equality (the values of their components).
The hashCode() Method
Generates a hash code based on the values of the record components.
The toString() Method
Produces a string representation of the record object in a human-readable format.
Since these methods are automatically generated, you don’t have to worry about writing them yourself, which saves you a significant amount of boilerplate code.
Equivalent Regular Java Class
Let’s see how the Person record class can be re-implemented using a regular Java class without the record feature:
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters (if needed)
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Person otherPerson)) return false;
return Objects.equals(name, otherPerson.name) &&
age == otherPerson.age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person[name=" + name + ", age=" + age + "]";
}
// Additional methods (if needed)
}
In this version, we define a regular Java class Person with private fields name and age. We explicitly implement the equals(), hashCode(), and toString() methods, which the record class provided automatically.
While this implementation achieves the same functionality, it introduces more boilerplate code, which can be a maintenance burden in the long run. That’s where Java Record Classes shine—they reduce the amount of boilerplate code and make the class more concise and readable.
Usage of Java Record Classes
Now, let’s revisit how we can use the Person record class from the previous example:
public class Main {
public static void main(String[] args) {
Person person1 = new Person("George", 18);
Person person2 = new Person("Edward", 28);
Person person3 = new Person("George", 18);
// Output: Person[name=George, age=18]
System.out.println(person1);
// Output: false
System.out.println(person1.equals(person2));
// Output: true
System.out.println(person1.equals(person3));
}
}
As you can see, we created three Person objects and demonstrated the usage of the toString() and equals() methods provided by the record class. The equals() method checks the structural equality of the objects based on their components (name and age).
Customizing Record Classes
Although record classes provide default implementations for the equals(), hashCode(), and toString() methods, you can customize their behavior if needed. For instance, if you want to exclude a specific component from the equality check, you can override the equals() method:
import java.util.Objects;
public record Person(String name, int age) {
// Additional members, if needed
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Person otherPerson)) return false;
return Objects.equals(name, otherPerson.name);
}
}
In this example, we override the equals() method to only compare the name field for equality, ignoring the age field.
Conclusion
Java Record Classes are a fantastic addition to the Java language, offering a concise and elegant way to create data classes without the boilerplate code. They make our code more readable, maintainable, and less error-prone by providing default implementations for common methods. As of Java 16, Record Classes are a standard feature, allowing developers to leverage this powerful capability in their projects.
When designing your next Java application, consider using Record Classes to handle data encapsulation efficiently and to focus more on the core logic of your code.
Sources:
I hope you found this code informative and useful. If you would like to receive more content, please consider subscribing to our newsletter!