Released on March 17, 2020, Java has officially released JDK 14, which is now available for download. This article focuses on one of the 16 new features in JDK 14: JEP 359: Records

Official ridicule is the deadliest

Back in February 2019, Brian Goetz, Java language architect, wrote an article (cr.openjdk.java.net/~briangoetz…) “, he joined many programmers in complaining that “Java is too verbose” or too much “red tape”, noting that developers wanting to create plain Data Carriers often have to write a lot of low-value, repetitive, error-prone code. Constructors, getters/setters, equals(), hashCode(), toString(), etc.

So much so that many people choose to use IDE features to generate this code automatically. Some developers choose to use third-party libraries such as Lombok to generate these methods, resulting in surprising behavior and poor debuggability.

So what exactly does Brian Goetz mean by pure data carriers? He gives a simple example:

final class Point {
    public final int x;
    public final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // state-based implementations of equals, hashCode, toString
    // nothing else
}
Copy the code

The Piont is a pure data carrier, representing a “point” containing x and y coordinates and providing only constructors, equals, hashCode, etc.

Then BrianGoetz came up with the idea that Java could represent this pure data carrier in a different way.

In fact, other object-oriented languages have long had separate definitions for this pure data carrier, such as case in Scala, data in Kotlin, and record in C#. These definitions, although semantically different, have in common the fact that some or all of the state of a class can be described directly in the class header, and that the class contains only pure data.

So, he asked if Java could also define a pure data carrier in the following way.

record Point(int x, int y) { }
Copy the code

God said record, and there it was

As God quips, we often need to write a lot of code to make a class useful. As follows:

  • The toString () method
  • HashCode () and equals() methods
  • Getter method
  • A common constructor

For such a simple class, these methods are usually boring, repetitive, and the kind of thing that can be easily generated mechanically (ides often provide this capability).

When you read someone else’s code, you might get even bigger. For example, someone might use the IDE-generated hashCode() and equals() to process all the fields of a class, but how can you be sure he wrote correctly without checking every line of implementation? What happens if fields are added during refactoring and methods are not regenerated?

Brian Goetz came up with the idea of using Record to define a pure data carrier, so a new feature is included in Java 14: EP 359: Records, written by Brian Goetz

The goal of Records is to extend the Java language syntax by providing a compact syntax for declarative classes for creating classes that are “fields, just fields, and nothing but fields.” By making this declaration on a class, the compiler can automatically create all methods and have all fields participate in methods like hashCode(). This is a preview feature in JDK 14.

A word against decompilation

The use of Records is relatively simple, just like defining a Java class:

record Person (String firstName, String lastName) {}
Copy the code

As above, we define a Person record with two components, firstName and lastName, and an empty class body.

So, this thing looks like a syntax candy, so how did he do that?

Let’s try compiling it first, using the –enable-preview parameter, since records is currently a preview feature in JDK 14.

> javac --enable-preview --release 14 Person.java
Note: Person.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
Copy the code

As mentioned earlier, Record is just a class whose purpose is to save and expose data. Let’s take a look at decompiling with Javap and get the following code:

public final class Person extends java.lang.Record {  
  private final String firstName;
  private final String lastName;
  public Person(java.lang.String, 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();
 }
Copy the code

By decompiling the resulting class, we can get the following information:

A Person class of final type (class) is generated, indicating that this class cannot be subclassed.

2. This class inherits from java.lang.Record, which is similar to the enumeration we create using enum

There are two private final properties in the class. Therefore, all properties in the class defined by Record should be of the private final type.

4. There is a public constructor whose input parameters are the two main attributes. If you look at the body of the method in bytecode, it contains the following code, which you’ll be familiar with:

public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}
Copy the code

5. There are two getter methods called firstName and lastName. This is different from the way JavaBean names are defined, and perhaps god wants to tell us that the Record definition is not a JavaBean.

6. It also automatically generates toString(), hashCode(), and equals() methods for us. It is worth mentioning that these three methods rely on InvokeDynamic to dynamically invoke the appropriate methods that contain implicit implementations.

You can play it this way

In the previous example, we simply created a record. Can there be other member variables and methods in the record? Let’s see.

1. We cannot add instance fields to record. However, we can add static fields.

record Person (String firstName, String lastName) {
    static int x;
}
Copy the code

2. We can define static methods and instance methods that manipulate the state of objects.

record Person (String firstName, String lastName) { static int x; public static void doX(){ x++; } public String getFullName(){ return firstName + " " + lastName; }}Copy the code

3. We can also add constructors.

record Person (String firstName, String lastName) { static int x; public Person{ if(firstName == null){ throw new IllegalArgumentException( "firstName can not be null !" ); } } public Person(String fullName){ this(fullName.split(" ")[0],this(fullName.split(" ")[1]) } }Copy the code

So, it is possible to add static fields/methods to record, but the question is, should we?

Remember, the goal behind the record rollout was to enable developers to group related fields together as single immutable data items without having to write lengthy code. This means that every time you want to add more fields/methods to your record, consider whether you should use a full class instead.

conclusion

Record solves a common problem with using classes as data wrappers. Pure data classes are dramatically reduced from a few lines of code to one.

However, Record is currently a preview language feature, which means that although it is fully implemented, it has not been standardized in the JDK.

The question is, if you use Java 14, will you still use Lombok? Oh no, you probably won’t be using it anytime soon, because you probably aren’t even familiar with Java 8 yet

References:

openjdk.java.net/jeps/359

Dzone.com/articles/a-…

Aboullaite. Me/Java – 14 – rec…

Cr.openjdk.java.net/~briangoetz…

About the author: Hollis, a person with a unique pursuit of Coding, is a technical expert of an Internet company, a personal technical blogger, who has read tens of millions of technical articles on the Internet, and co-author of three Courses for Programmers.