Java records demystified
2020-04-21
Java has always been verbose and almost all of us have accepted the fact and are used to its verbosity. We generally take the help of our super-intelligent IDEs or libraries like lombok to generate most of the boilerplate code.
Still, when it comes down to doing super simple things like creating a data carrier class (which is as dumb as it sounds), a simple DTO or a value object we always feel the need to have something quick and embedded in Java itself.
Presenting, records
records are a new type declaration in Java. But, before we move ahead let me make it very clear that records are not something that has been introduced to address problems like boilerplate, properties class or annotation-driven code generation, etc. Instead, it has been designed with a more thoughtful goal of representing data as data
Remember, records are still a preview feature and you'll have to use the
--enable-previewflag (explained below) when compiling/executing the programs from this post. You will also need JDK 14.
javac --release 14 --enable-preview Example.javajava --enable-preview ExampleCreate a record
Here is a record in its simplest form.
// Point.java
record Point(int x, int y){};Yes, that's it. Yes, it's Java. Yes, pure Java. No, no libraries, annotations or frameworks.
A record has a name (Point in this case), state description which declares the components ((int x, int y)) and an optional body (notice {}).
Now, let's see what Java generates for you after you compile your code.
// decompiled Point.class
final class Point extends java.lang.Record {
private final int x;
private final int y;
public Point(int x, int y) { /* compiled code */ }
public java.lang.String toString() { /* compiled code */ }
public final int hashCode() { /* compiled code */ }
public final boolean equals(java.lang.Object o) { /* compiled code */ }
public int x() { /* compiled code */ }
public int y() { /* compiled code */ }
}A lot of things are pretty evident from the code above
- a record is just another class that extends
java.lang.Record - records are implicitly immutable
- a public all args constructor with signature same as the state description
- public getters
- implementation of hashCode & equals
- implementation of toString
Superb! right?
How to use records?
records enjoy all the benefits of a Java class with some restrictions (which we will see later on). That said, a record can be instantiated with the new keyword and the methods can be accessed like any other object's.
// Application.java
public class Application {
public static void main(String[] args) {
Point point = new Point(2, 4);
System.out.println("point = " + point.toString());
System.out.println("point.x = " + point.x());
System.out.println("point.y = " + point.y());
}
}
// output
point = Point[x=2, y=4]
point.x = 2
point.y = 4Equality in records
As we saw earlier, a record implicitly generates hashCode and equals and if we go by this default implementation, then, two records are equal to each other when they're of the same type and contain the same state. An example will make it more clear
// Application.java
public class Application {
public static void main(String[] args) {
Point point1 = new Point(1, 2);
Point point2 = new Point(1, 2);
System.out.println("point1.equals(point2) = " + point1.equals(point2));
}
}
// output
point1.equals(point2) = truepoint1 and point2 are equal to each other for being of the same type (Point) and containing the same states (x = 1 & y = 2)
Restrictions
records do have some restrictions as well
- they cannot extend any other class. That's because records implicitly extends
java.lang.Record
record Point(int x, int y) extends SomeClass {}; // Error - not allowed- they cannot have explicit instance variables other than the private final fields which are the part of components of the state description
record Point(int x, int y) extends SomeClass {
private final int z; // Error - not allowed
private int z; // Error - not allowed
};Features
Apart from the restrictions above, records do behave like normal classes
- they can have static fields, methods, initializers
// Point.java
record Point(int x, int y) {
public static final double PI = 3.14; // allowed
// allowed
public static double getPI() {
return PI;
}
// allowed
static {
System.out.println("Printed in a static initializer");
}
};- they can have instance methods
// Point.java
record Point(int x, int y) {
// allowed
public int add() {
return x + y;
}
};- they can have explicit canonical constructors without a formal parameter list. This allows performing validations & normalizations
// Point.java
record Point(int x, int y) {
// allowed
public Point {
if (x < y) {
throw new IllegalArgumentException("x cannot be less than y");
}
}
};- Yes, they can be generic too
// Point.java
record Point<T>(T x, T y) {};
// Application.java
public class Application {
public static void main(String[] args) {
Point<String> point = new Point<>("A", "B");
}
}Final thoughts
records being so less verbose and concise while still enjoying a normal class's features are a great addition to Java. Developers will surely find it beneficial at various places and will be able to move very quickly when representing data in their code.