DEV Community

loading...
Cover image for ๐Ÿš€ Java 14 Records ๐Ÿ’พ (Preview)

๐Ÿš€ Java 14 Records ๐Ÿ’พ (Preview)

cchacin profile image Carlos Chacin โ˜•๐Ÿ‘ฝ Originally published at carloschac.in ใƒปUpdated on ใƒป4 min read

๐Ÿ Records ๐Ÿ’พ

Record 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) { }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ Writing our first Java Record ๐Ÿ’พ

public record Person(
    String firstName,
    String lastName,
    String address,
    LocalDate birthday,
    List<String> achievements) {
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ป Compile it with javac

$ javac --enable-preview -source 14 Person.java  
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ป 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>
Enter fullscreen mode Exit fullscreen mode
$ mvn compile
Enter fullscreen mode Exit fullscreen mode

๐Ÿšง What's generated by the compiler?

$ javap -p Person.class
Enter fullscreen mode Exit fullscreen mode
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();
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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

1) Create a safe copy of the data type in the record's constructor.
2) 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.

Related article:

Discussion (0)

Forem Open with the Forem app