31. Records
Records (Java 16+) are transparent, immutable data carriers that automatically generate a canonical constructor, component accessors, equals(), hashCode(), and toString() from the record header.
Explanation
A record declaration record Point(int x, int y) {} generates: a private final int x; private final int y; field for each component, a canonical constructor Point(int x, int y) that assigns all fields, accessor methods x() and y() (not getX()/getY()), and correct equals(), hashCode(), and toString().
Records are implicitly final (cannot be extended), cannot extend other classes (they implicitly extend java.lang.Record), and all fields are private final. They can implement interfaces, declare static fields and methods, declare instance methods, and have multiple constructors.
The compact constructor lets you validate or normalise arguments without repeating assignments โ parameter assignments happen automatically after the compact constructor body: record Range(int lo, int hi) { Range { if (lo > hi) throw new IllegalArgumentException(); } }.
Example
import java.util.List;
public class RecordsDemo {
// Basic record
record Point(int x, int y) {
// Compact constructor: validate, then assignments happen automatically
Point {
if (Double.isNaN(x) || Double.isNaN(y)) throw new IllegalArgumentException();
}
// Instance method
double distanceTo(Point other) {
int dx = this.x - other.x, dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
// Static factory
static Point origin() { return new Point(0, 0); }
}
// Record implementing an interface
interface Shape { double area(); }
record Circle(double radius) implements Shape {
// Compact constructor for validation
Circle { if (radius <= 0) throw new IllegalArgumentException("radius must be positive"); }
@Override public double area() { return Math.PI * radius * radius; }
}
// Generic record
record Pair<A, B>(A first, B second) {}
public static void main(String[] args) {
Point p1 = new Point(1, 2);
Point p2 = new Point(3, 4);
System.out.println(p1); // Point[x=1, y=2]
System.out.println(p1.x()); // 1 (accessor, not getX())
System.out.println(p1.equals(new Point(1, 2))); // true
System.out.printf("Distance: %.2f%n", p1.distanceTo(p2));
Circle c = new Circle(5);
System.out.printf("Circle area: %.2f%n", c.area());
// Validation
try { new Circle(-1); }
catch (IllegalArgumentException e) { System.out.println("Caught: " + e.getMessage()); }
Pair<String, Integer> pair = new Pair<>("Alice", 30);
System.out.println(pair); // Pair[first=Alice, second=30]
// Records work great with streams and collections
List<Point> points = List.of(new Point(0, 0), new Point(3, 4), new Point(1, 1));
points.stream()
.filter(p -> p.x() > 0)
.forEach(System.out::println);
}
}Sources
- https://openjdk.org/jeps/395 โ JEP 395: Records (standard)
- https://dev.java/learn/records/ โ Records guide on dev.java