JDK 14でPreview ReleaseされるRecordsが素敵なのでJShellで試そう

## はじめに

DZoneで [JDK 14 Rampdown: Build 27](https://dzone.com/articles/jdk-14-rampdown-build-27) を読むと

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

と書いており、とうとう試せるようになった **Records** をJShellで試してみました。

なお、 JDK14 のリリース時や、その後の Records の正式リリース時に内容は変わっているかもしれませんので、ご容赦ください。

## JEP 359: Records

[JEP 359: Records](https://openjdk.java.net/jeps/359)[^1] はJavaの文法改善を目的としたProject Amberの1つ [^2] です。

半年ごとにリリースされるようになった Java ですが、当初のもくろみどおり、いろいろモダンな言語も参考に改善が進んでいます。

そんな中で **Records** は、TypeScriptの [Constructor Shorthand](https://maksimivanov.com/posts/typescript-constructor-shorthand/) や、Kotrinの [Primary Constructor](https://kotlinlang.org/docs/reference/classes.html#constructors) のコンストラクタ引数がフィールド変数の定義を兼ねる簡単な記法[^2]のように、 **レコード名と引数（レコードコンポーネント）の定義で、データの入れ物となるデータクラスを定義する仕組み**です[^3]。

これが使えるようになると、戻り値、DTO（Data Transfer Objects）、Stream APIの操作、etc...などで「ただの"データの入れ物"を作るときにも、クラス（や適切なフィールド・メソッド）を作らないといけない」という現状に、「データの入れ物として明示的にデータクラスを作れば良い」という選択肢が加わります。

## Recordsの使い方

### 基本的な使い方

Recordsの記法は、Qiitaでも "[Amberで検討されているJava構文の変更](https://qiita.com/nowokay/items/6b8f109fe9dec4037d7e) " や "[Kotlinと今後のJavaはどっちがいい？](https://qiita.com/YutaSaito1991/items/597491504fe9c21e9913)" で解説されていますが、

```java
record Person(String name) {};
```

と書くと、

```java
final class Person {

  // フィールド変数
  public final String name;

  // 引数つきコンストラクタ
  public Person(String name) {
    this.name = name;
  }

  // フィールド変数にあわせて Getter, hashCode, equals, toString が自動実装される
  // getterはfluentな（フィールド変数名と同じ）メソッド名。
  public String name() {
    return this.name;
  }
  public int hashCode() { /* 中略、等価性を保つように実装 */ }
  public boolean equals() { /* 中略、等価性を保つように実装 */ }
  public String toString() { /* 中略、クラス名・フィールド名・値を返す */ }

  // 以上
}
```

という、イミュータブルなクラスと同等のデータクラスが自動的に定義されたことになります。便利。

ただし、これは**単なるクラスの速記法や、lombokの様なボイラープレートを解決する記法ではなくて[^4]、「データの入れ物」という新しい仕組み（enumの様な制限のあるクラスの仕組み）を作ることが目的で[^5]、冗長性の解消はあくまで結果である**とJEPにも書いてあります。

実際に使うときには、このあたりを意識した方がよさそうです。

### インターフェースの宣言

classと同様に、 implements を使います。

例えば、Serializable インターフェースを定義したいときは、

```java
record Person(String name) implements Serializable {};
```

と書けば

```java
final class Person implements Serializable {
 // 略
}
```

とシリアライズ可能なデータクラス定義されたことになります。

### 継承

スーパークラスになることも、サブクラスになることもできません。先述のとおり、あくまで従来のクラスの速記法ではないというスタンスです。

### 独自のコンストラクタやメソッドを増やす

自動的に定義されるコンストラクタやメソッド以外を増やしたい場合は、ボディ（`{}`の中）に記載します。

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

上記の例であれば、recordで自動定義される内容に加えて、引数なしコンストラクタ、getNameLengthメソッドが用意されます。

増やせるものは、コンストラクタ、クラスメソッド、クラスフィールド、クラスイニシャライザ、インスタンスメソッド、インスタンスイニシャライザです。データクラスの形を変えることがないよう、インスタンスフィールドは追加できません[^6]。

## JShellで使ってみる

JShellで使ってみます。試したPCには、執筆時にSDKMAN!でインストールできる JDK 14 Build 28 （`14.ea.28-open`） が入っています。

```sh
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)
```

以下、実際に再現してみたい方は `jshell>` とか `...>` は除いてコピペしてみてください。

### 1. jshellの起動

プレビュー版の機能を有効にして、JShellを起動します。

```sh
jshell --enable-preview
```

### 2. record でデータクラスを定義してインスタンス化する

`String name` をレコードコンポーネント（つまり、インスタンスフィールド）に持つ `Person` データクラスを定義してみます。

```java
jshell> record Person(String name) {} ;
```

特に表示無く、データクラスが完成するので、インスタンス化してみます。

```java
jshell> var someone = new Person("Yamada");
```

```
someone ==> Person[name=Yamada]
```

someone変数でインスタンスが参照できるようになりました。

データを取り出したり、メソッドを使ってみましょう。

```java
jshell> System.out.println(someone.name());
```

```
Yamada
```

```java
jshell> System.out.println(someone.toString());
```

```
Person[name=Yamada]
```

等価性も確認してみましょう。

```java
var other = new Person("Yamada");
   ...> someone.equals(other);
```

```
other ==> Person[name=Yamada]
$7 ==> true
```

```java
jshell> other = new Person("Ichikawa");
   ...> someone.equals(other);
```

```
other ==> Person[name=Ichikawa]
$9 ==> false
```

インスタンスが異なっても、フィールドの値の等価性が判定できていますね。

### 3. インターフェースを設定してみる

Personと、SerializableなPersonを作って、インターフェース型を判定してみましょう。

```java
someone instanceof Serializable;
```

```
|  エラー:
|  不適合な型: Personをjava.io.Serializableに変換できません:
|  someone instanceof Serializable;
|  ^-----^
```

```java
jshell> record SerializablePerson(String name) implements Serializable {} ;
   ...> var nextOne = new SerializablePerson("Sato");
   ...> nextOne instanceof Serializable;
```

```
nextOne ==> SerializablePerson[name=Sato]
$16 ==> true
```

`implements Serializable` している `SerializablePerson` のインスタンスは、 `instanceof` の結果が `true` になっており、インターフェースの設定が効いていることがわかります。

### 4. 独自のコンストラクタやメソッドを増やす

Personのボディでデフォルトコンストラクタを増やしたり、レコードコンポーネントを使うメソッドを追加してみましょう。

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

Personがデフォルトコンストラクタでインスタンス化されると、`Jhon Doe` で初期化されるようにしています。また、lengthを返すインスタンスメソッドも追加しています。

インスタンス化して使ってみましょう。

```java
jshell> var someone = new Person();
   ...> someone.name();
   ...> someone.getNameLength();
```

```
someone ==> Person[name=Jhon Doe]
$3 ==> "Jhon Doe"
$4 ==> 8
```

デフォルトコンストラクタの設定や、増やしたメソッドが動作していますね。

### 5. その他

#### Class.isRecord()

record から作られたデータクラスかどうかは、 isRecord で調べられます。

```java
jshell> Person.class.isRecord();
```

```
$8 ==> true
```

```java
jshell> String.class.isRecord();
```

```
$9 ==> false
```

#### Class.getRecordComponents()

record から作られたデータクラスが持つレコードコンポーネントを、 RecordComponent の配列で返します。

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

jshell> Range.class.getRecordComponents();
```

```
$12 ==> RecordComponent[2] { int lo, int hi }
```

### 6. 利用例

利用例として、[JEPの背景文章](https://cr.openjdk.java.net/~briangoetz/amber/datum.html)にあった、Streamの中間操作の例を試してみました。

```java
record Person(String name) {} ;

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

//本来はどこかのデータソースから取り出してくる想定と思います
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());
```

JShellの結果はこちら。

```
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]]
```

若干、実行結果がJShellで省略されていますが、Yamada, Ichikawa, Sato, Tanakaを持つ（データ）クラスのリストを、PersonXに変換、hash順にソートする仕組みです[^7]。

Person、PersonX は **record で作られたデータクラスですが、通常のクラスとは異なるもののクラスの枠組みは外れていないので、利用時も通常のクラスと混ぜて使ったり、これまでの文法を大きく変えずに利用できます** 。

旧来であれば、Person, PersonXはそれぞれクラス定義をして、ミュータブル/イミュータブルにあわせてメソッドを用意したり... という手はずが必要でしたが、「データの入れ物」として扱えるのなら、

```java
record Person(String name) {} ;
```

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

だけで記述が済みます。

また、クラスなのか「データの入れ物」なのかの区別が必要な時も、Class#isRecord で判断できます。

このように **Records を使うことで、「（単なる）データの入れ物」と従来のクラスの区別の仕方をJavaに取り入れて、かつ記述が簡潔に済む** ようになりそうです。


## おわりに

JShellを使って、JDK14からプレビューリリースされる Records を、JShellで体験してみました。

私も正直、最初は「ボイラープレート対策！素敵！」と思ってたんですが、JEPの文章を読んでみたり実際に使ってみると、文中にあった「enumの時と同じように現れた、新しい "データの入れ物" という仕組み」という側面の方がしっくりくるようになりました。

もちろん、Kotlinの data class をさっさと混在して使ってしまう、という手もありますが、JEP 359は、Javaが標準でも次第に使いやすくなっていく象徴の様にも思えます。

JDK14、そしてその後の正式リリースが待ち通しいです。


[^1]: 英語が苦手な方は [kagamihogeの日記 - JEP 359: Records (Preview)をテキトーに訳した](http://kagamihoge.hatenablog.com/entry/2019/09/18/220213) で概要をつかみましょう。
[^2]: Amberの具体的な内容は きしだなおきさんの [Amberで検討されているJava構文の変更](https://qiita.com/nowokay/items/6b8f109fe9dec4037d7e) を読むとわかりやすいです。
[^3]: [JEPの背景文章](https://cr.openjdk.java.net/~briangoetz/amber/datum.html)にはKotlinのdata class、Scalaの case class、C#のrecord classがデータクラスの例にあげられていました。
[^4]: [JEP 359: Records](https://openjdk.java.net/jeps/359)より、<br>*"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]: [JEP 359: Records](https://openjdk.java.net/jeps/359)より、<br>*Records are a new kind of type declaration in the Java language. Like an enum, a record is a restricted form of class.*
[^6]: [JEP 359: Records](https://openjdk.java.net/jeps/359)より、<br>*"The record's body may declare static methods, static fields, static initializers, constructors, instance methods, instance initializers, and nested types."* 
[^7]: 元のコードにあったTypoを修正し、わかりやすい用にlimitを外したり、peekを付けたりしています。