Carlos Chacin
Software Engineering Experiences
๐ Java 14 Records ๐พ (Preview)
Posted at April 17, 2020 / Carlos Chacin
17 Apr 2020 Carlos ChacinRecord is a new kind of type declaration in the Java language. Like an enum, a record is a restricted form of class. It declares its representation and commits to an API that matches that representation. Records give up a freedom that classes usually enjoy: the ability to decouple API from representation. In return, records gain a significant degree of concision.
A record has a name and a state description. The state description declares the components of the record. Optionally, a record has a body. For example:
record Point(int x, int y) { }
Letโs get started
โฌ๏ธ Download OpenJDK 14.0.1
๐ฅ Set the JAVA_HOME to point to the downloaded JDK 14
$ export JAVA_HOME=/path/to/jdk14
$ export PATH=$JAVA_HOME/bin:$PATH
๐ Writing our first Java Record ๐พ
public record Person(
String firstName,
String lastName,
String address,
LocalDate birthday,
List<String> achievements) {
}
๐ป Compile it with javac
$ javac --enable-preview -source 14 Person.java
๐ป Compile it with maven
Add the following configuration for the compiler plugin in your pom.xml
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>14</release>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
$ mvn compile
๐ง Whatโs generated by the compiler?
$ javap -p Person.class
public final class Person extends java.lang.Record {
private final java.lang.String firstName;
private final java.lang.String lastName;
private final java.lang.String address;
private final java.time.LocalDate birthday;
private final java.util.List<java.lang.String> achievements;
public Person(java.lang.String, java.lang.String, java.lang.String, java.time.LocalDate, java.util.List<java.lang.String>);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public java.lang.String firstName();
public java.lang.String lastName();
public java.lang.String address();
public java.time.LocalDate birthday();
public java.util.List<java.lang.String> achievements();
}
Because records make the semantic claim of being simple, transparent holders for their data, a record acquires many standard members automatically:
- A private final field for each component of the state description;
- A public read accessor method for each element of the state description, with the same name and type as the component;
- A public constructor, whose signature is the same as the state description, which initializes each field from the corresponding argument;
- Implementations of equals and hashCode that say two records are equal if they are of the same type and contain the same state; and
- An implementation of toString that includes the string representation of all the record components, with their names.
โ ๏ธ Shallowly Immutable Data
Similarly to the tests that we did in the article Immutables/AutoValue/Lombok Which One? where we check the default behavior in terms of immutability for those libraries, with the below test we demonstrate the statement mentioned in the JEP 359: Records:
Records provide a compact syntax for declaring classes, which are transparent holders for shallowly immutable data.
๐ Test Immutability
@Test
void immutability() {
// Create a mutable list with 1 element
var achievements1 = new ArrayList<String>();
achievements1.add("Speaker");
achievements1.add("Blogger");
var achievements2 = List.of("Speaker", "Blogger");
// Create person 1, assigning the list1
var person1 = new Person(
"John",
"Doe",
"USA",
LocalDate.of(1990, 11, 11),
achievements1
);
// Create person 2, assigning the list2
var person2 = new Person(
"John",
"Doe",
"USA",
LocalDate.of(1990, 11, 11),
achievements2
);
// Compare the 2 objects
// Test passes since the fields contain the same values
assertThat(person1).isEqualTo(person2);
// Mutate the list used on Model 1
achievements1.add("AnotherValue");
// Compare the 2 objects:
// - PASSES objects are NOT EQUAL for Records ๐ฎ ๐ด
assertThat(person1).isNotEqualTo(person2);
}
There are two ways of guaranteeing immutability when using records with mutable data types in their signature or when the data type is an interface, and we are not sure about the implementation, i.e., java.util.Date
or java.util.List
- Create a safe copy of the data type in the recordโs constructor.
- Pass only immutable objects when creating the records.
๐ Conclusions
- โ Records can reduce several lines of code to a one-liner.
- โ With JDK 14, we can prescind of using some code generation libraries to minimize boilerplate code.
- โ
A great option for:
- ๐ Tree nodes
- ๐ DTOs
- ๐ Compound map keys
- ๐ Messages
- ๐ Value wrappers
- ๐ Discriminated Entities
- โ ๏ธ Records are only Shallowly Immutables, which means that we have to take into consideration the data types passed to them if we want to guarantee immutability.
- โ ๏ธ Records are a preview language feature, and it is not yet a standard in the JDK.