Java Records, introduced in Java 14 as a preview feature and finalized in Java 16, provide a concise way to define immutable data carrier classes. They reduce boilerplate code needed for simple data structures by automatically providing implementations for methods like equals()
, hashCode()
, and toString()
, as well as accessor methods for the fields. This article will explore the features and usage of Java Records with examples.
What are Java Records?
A Java Record is a special kind of class in Java that is designed to hold immutable data. It is particularly useful for modeling data as tuples or simple objects that primarily exist to store values. Records are a good fit for situations where the main purpose of the class is to be a simple data carrier without additional logic.
Key Characteristics of Java Records:
- Immutable: Once created, the data stored in a record cannot be changed.
- Concise Syntax: Records require less code to define compared to regular classes.
- Predefined Methods: Automatically includes implementations for
equals()
,hashCode()
,toString()
, and accessor methods.
Syntax
The syntax for defining a record is straightforward:
public record RecordName(type fieldName, ...) {
// optional: you can add methods and static fields
}
Example: Defining a Simple Record
Let’s create a simple record to represent a Point
in a 2D space:
public record Point(int x, int y) { }
This single line defines a record class Point
with two fields, x
and y
. The Java compiler automatically generates the following:
- A constructor:
public Point(int x, int y)
- Accessor methods:
public int x()
andpublic int y()
- Implementations of
equals(Object obj)
,hashCode()
, andtoString()
Using the Point
Record
Here’s how you can use the Point
record:
public class Main {
public static void main(String[] args) {
Point point = new Point(5, 10);
System.out.println(point); // Output: Point[x=5, y=10]
System.out.println("X: " + point.x()); // Output: X: 5
System.out.println("Y: " + point.y()); // Output: Y: 10
Point anotherPoint = new Point(5, 10);
System.out.println(point.equals(anotherPoint)); // Output: true
}
}
Additional Features of Records
Custom Constructors
While records automatically provide a canonical constructor, you can define custom constructors for additional validation or initialization:
public record Circle(double radius) {
public Circle {
if (radius <= 0) {
throw new IllegalArgumentException("Radius must be positive");
}
}
}
This compact constructor ensures that any Circle
created has a positive radius.
Custom Methods
You can add methods to a record to provide additional behavior:
public record Rectangle(int width, int height) {
public int area() {
return width * height;
}
}
With this Rectangle
record, you can calculate the area directly:
Rectangle rectangle = new Rectangle(4, 5);
System.out.println("Area: " + rectangle.area()); // Output: Area: 20
Static Fields and Methods
Records can have static fields and methods, which can be useful for constants or utility functions:
public record Temperature(double celsius) {
public static Temperature fromFahrenheit(double fahrenheit) {
return new Temperature((fahrenheit - 32) * 5 / 9);
}
}
Nested Records
Records can be nested within other classes or records, allowing for more complex data structures:
public class Geometry {
public record Line(Point start, Point end) { }
public static void main(String[] args) {
Point p1 = new Point(1, 2);
Point p2 = new Point(3, 4);
Line line = new Line(p1, p2);
System.out.println(line); // Output: Line[start=Point[x=1, y=2], end=Point[x=3, y=4]]
}
}
Benefits of Using Records
- Readability: Reduces boilerplate code, making the code more readable and maintainable.
- Simplicity: Provides a clear and concise way to represent simple data structures.
- Immutability: Encourages the use of immutable objects, which are easier to reason about and less error-prone in concurrent programming.
Limitations
- Immutability: Once created, record fields cannot be modified, which might not be suitable for all use cases.
- Inheritance: Records cannot extend other classes (other than
java.lang.Record
implicitly) and cannot be extended.
Conclusion
Java Records offer a powerful tool for defining immutable data carrier classes with minimal boilerplate. They enhance code readability and maintainability, making them a valuable addition to the Java language for developers working with data-centric applications. By leveraging records, developers can create clean and efficient code while focusing on the core logic of their applications.