[JAVA] The Records that are preview released in JDK 14 are nice, so let's try it with JShell

Introduction

If you read JDK 14 Rampdown: Build 27 in DZone

One might say that JDK 14 Early Access Build 27 is the "records build."

I tried ** Records ** with JShell, which I finally got to try.

Please note that the content may have changed when JDK14 was released and when Records was officially released after that.

JEP 359: Records

JEP 359: Records [^ 1] is one of the Project Ambers [^ 2] aimed at improving Java grammar.

Java is released every six months, but as originally planned, improvements are being made with reference to various modern languages.

Meanwhile, ** Records ** includes TypeScript's Constructor Shorthand and Kotrin's Primary Constructor. As in the simple notation [^ 2] where the constructor argument of /docs/reference/classes.html#constructors also serves as the definition of the field variable, ** the definition of the record name and argument (record component), and the data container It is a mechanism to define the data class ** [^ 3].

Once this is available, classes (and appropriate field methods) can be used to create "just a" data container "" with return values, DTOs (Data Transfer Objects), Stream API operations, etc ... In addition to the current situation that "you have to create a data class", the option "you can explicitly create a data class as a data container" is added.

How to use Records

Basic usage

The notation of Records is also used in Qiita, "Changes in Java syntax being considered by Amber" and "Which is better, Kotlin or Java in the future? ", but

record Person(String name) {};

If you write

final class Person {

  //Field variables
  public final String name;

  //Constructor with arguments
  public Point(String name) {
    this.name = name;
  }

  //Getter according to the field variable, hashCode, equals,toString is automatically implemented
  //getter is a fluent (same as field variable name) method name.
  public String name() {
    return this.name;
  }
  public int hashCode() { /*Omitted, implemented to maintain equivalence*/ }
  public boolean equals() { /*Omitted, implemented to maintain equivalence*/ }
  public String toString() { /*Omitted, returns class name / field name / value*/ }

  //that's all
}

That is, a data class equivalent to the immutable class is automatically defined. Convenient.

However, this is not just a shorthand notation for classes or a notation for solving boilerplate like lombok [^ 4], but a new mechanism called "data container" (a mechanism for restricted classes like enums). ) Is the purpose [^ 5], and the elimination of redundancy is just the result **, which is also written in JEP.

When actually using it, it seems better to be aware of this area.

Interface declaration

Use implements as well as class.

For example, if you want to define a Serializable interface,

record Person(String name) implements Serializable {};

If you write

final class Person implements Serializable {
 //Abbreviation
}

This means that a serializable data class has been defined.

Inheritance

It cannot be a superclass or a subclass. As mentioned earlier, this is a stance that is not a traditional class shorthand.

Increase your own constructors and methods

If you want to increase the number of constructors and methods other than those that are automatically defined, write them in the body (inside {}).

record Person(String name){
  Person() {
    this("Jhon Doe");
  }
  public int getNameLength() {
    return name.length();
  }
} 

In the above example, in addition to the contents automatically defined by record, a no-argument constructor and getNameLength method are prepared.

You can add constructors, class methods, class fields, class initializers, instance methods, and instance initializers. Instance fields cannot be added so as not to change the shape of the data class [^ 6].

Try using it with JShell

I will use it with JShell. The PC I tried contains JDK 14 Build 28 (14.ea.28-open) that can be installed with SDKMAN! At the time of writing.

java -version
openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+28-1366)
OpenJDK 64-Bit Server VM (build 14-ea+28-1366, mixed mode, sharing)

Below, if you want to actually reproduce it, please copy and paste except for jshell> and ...>.

1. Start jshell

Start JShell with the preview version feature enabled.

jshell --enable-preview

2. Define and instantiate a data class in record

Let's define a Person data class that has a String name in its record component (that is, an instance field).

jshell> record Person(String name) {} ;

Since the data class is completed without any display, let's instantiate it.

jshell> var someone = new Person("Yamada");
someone ==> Person[name=Yamada]

You can now reference an instance with the someone variable.

Let's retrieve the data and use the method.

jshell> System.out.println(someone.name());
Yamada
jshell> System.out.println(someone.toString());
Person[name=Yamada]

Let's also check the equivalence.

var other = new Person("Yamada");
   ...> someone.equals(other);
other ==> Person[name=Yamada]
$7 ==> true
jshell> other = new Person("Ichikawa");
   ...> someone.equals(other);
other ==> Person[name=Ichikawa]
$9 ==> false

Even if the instances are different, you can judge the equivalence of the field values.

3. Try setting the interface

Let's create a Person and a Serializable Person to determine the interface type.

someone instanceof Serializable;
|error:
|Incompatible type:Person java.io.Cannot convert to Serializable:
|  someone instanceof Serializable;
|  ^-----^
jshell> record SerializablePerson(String name) implements Serializable {} ;
   ...> var nextOne = new SerializablePerson("Sato");
   ...> nextOne instanceof Serializable;
nextOne ==> SerializablePerson[name=Sato]
$16 ==> true

For instances of SerializablePerson that are ʻimplements Serializable, the result of ʻinstanceof is true, indicating that the interface settings are in effect.

4. Add your own constructors and methods

Let's increase the default constructor in the Person's body and add a method that uses the record component.

jshell> record Person(String name) {
   ...>         Person() {
   ...>             this("Jhon Doe");
   ...>         }
   ...>         public int getNameLength() {
   ...>             return name.length();
   ...>         }
   ...>     }

When Person is instantiated with the default constructor, it is initialized with Jhon Doe. We've also added an instance method that returns length.

Let's instantiate and use it.

jshell> var someone = new Person();
   ...> someone.name();
   ...> someone.getNameLength();
someone ==> Person[name=Jhon Doe]
$3 ==> "Jhon Doe"
$4 ==> 8

The default constructor settings and the increased methods are working.

5. Other

Class.isRecord()

You can check isRecord to see if it is a data class created from record.

jshell> Person.class.isRecord();
$8 ==> true
jshell> String.class.isRecord();
$9 ==> false

Class.getRecordComponents()

Returns the record component of the data class created from record as an array of RecordComponent.

jshell> record Range(int lo, int hi) {};

jshell> Range.class.getRecordComponents();
$12 ==> RecordComponent[2] { int lo, int hi }

6. Usage example

As a usage example, I tried an example of intermediate operation of Stream in JEP background text.

record Person(String name) {} ;

record PersonX(Person p, int hash) {
  PersonX(Person p) {
    this(p, p.name().toUpperCase().hashCode());
  }
}

//I think it's supposed to be taken from some data source
var list = List.of(new Person("Yamada"), 
  new Person("Ichikawa"), 
  new Person("Sato"), 
  new Person("Tanaka"));
       
list.stream()
  .map(PersonX::new)
  .sorted(Comparator.comparingInt(PersonX::hash))
  .peek(System.out::println)
  .map(PersonX::p)
  .collect(Collectors.toList());

Click here for J Shell results.

list ==> [Person[name=Yamada], Person[name=Ichikawa], Pers ... ato], Person[name=Tanaka]]
PersonX[person=Person[name=Tanaka], hash=-1827701194]
PersonX[person=Person[name=Yamada], hash=-1684585447]
PersonX[person=Person[name=Ichikawa], hash=-159644485]
PersonX[person=Person[name=Sato], hash=2537801]
$16 ==> [Person[name=Tanaka], Person[name=Yamada], Person[name=Ichikawa], Person[name=Sato]]

Although the execution result is slightly omitted in JShell, it is a mechanism to convert the list of (data) classes with Yamada, Ichikawa, Sato, Tanaka to PersonX and sort them in hash order [^ 7].

Person and PersonX are data classes made with ** record, but they are different from normal classes, but the framework of the class is not out of the scope, so even when using it, you can mix it with normal classes and make the grammar so far larger. You can use it without changing **.

In the old days, Person and PersonX had to define classes and prepare methods according to mutable / immutable ... but if it can be treated as a "data container",

record Person(String name) {} ;
record PersonX(Person p, int hash) {
  PersonX(Person p) {
    this(p, p.name().toUpperCase().hashCode());
  }
}

Just write it.

You can also use Class # isRecord to determine if you need to distinguish between a class and a "data container".

By using ** Records in this way, it seems that it will be possible to incorporate into Java the way of distinguishing between "(mere) data container" and conventional classes, and to keep the description simple **.

in conclusion

Using JShell, I tried to experience Records previewed from JDK14 with JShell.

To be honest, at first I thought "Boilerplate measures! Nice!", But when I read the JEP text and actually used it, it appeared in the same way as the "enum" in the text. , The aspect of the new "data container" mechanism has come to fit better.

Of course, there is a way to quickly mix and use Kotlin's data class, but JEP 359 seems to be a symbol that Java is becoming easier to use even if it is standard.

We are looking forward to JDK14 and the official release after that.

[^ 1]: For those who are not good at English kagamihoge's diary --JEP 359: Translated Records (Preview) into text Let's get an overview with. [^ 2]: The specific content of Amber is easy to understand if you read Java syntax changes being considered by Amber by Naoki Kishida. .. [^ 3]: Background text of JEP has Kotlin data class, Scala case class, and C # record class. It was given as an example of a data class. [^ 4]: From JEP 359: Records,
* "While it is superficially tempting to treat records as primarily being about boilerplate reduction, we instead choose a more semantic goal: modeling data as data. (If the semantics are right, the boilerplate will take care of itself.) "* [^ 5]: From JEP 359: Records,
* Records are a new kind of type declaration in the Java language. Like an enum, a record is a restricted form of class. * [^ 6]: From JEP 359: Records
* "The record's body may declare static methods, static fields, static initializers, constructors, instance methods, instance initializers, and nested types. "* [^ 7]: I modified the Typo that was in the original code, removed the limit and added peek for clarity.

Recommended Posts

The Records that are preview released in JDK 14 are nice, so let's try it with JShell
Java 10 (JDK 10) was released on March 20, 2018, so let's try Hello World.
About Records preview added in Java JDK 14
In Java Try-with-Resources, even if you return in the try clause, it will be closed properly, so let's return without worrying
Apple Silicon compatible version of Docker Desktop (Preview) has been released to the public, so I will try it