S-JIS[2020-03-21/2021-03-21] 変更履歴

レコード(Java14〜15)

Java14〜15のレコード(プレビュー版)について。


概要

2020/3/17にリリースされたJava14で、プレビュー版としてレコードが定義できるようになった。
レコードは、単純に値を保持するだけのイミュータブル(不変)なクラス。

クラスが「class クラス名」で定義するのと同様に、レコードは「record レコード名」で定義する。
このため、recordというクラス名は付けられなくなった。(recordというクラスを作ろうとするとコンパイルエラー)(普通はクラス名は先頭を大文字にするので、あまり関係ないが)
(recordという変数名やパッケージ名は以前と変わらず使用できる)


レコードはJava14〜15ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。

> javac --enable-preview --release 14 Example.java
> java --enable-preview Example

レコード定義方法

〔public〕 record レコード名(型 変数名,…) 〔implements インターフェース名,…〕 {
	〔コンストラクター・メソッド定義〕
}

レコード名の直後の丸括弧の部分をレコードヘッダーと呼ぶ。[/2020-09-22]
レコードヘッダー内の「型 変数名」のことをレコードコンポーネント(RecordComponent)と呼ぶ。
ここから、フィールドと、値を取得するメソッドが生成される。
各レコードコンポーネントの名前が「値を取得するメソッド」の名前(とフィールド名)になる。
レコードコンポーネントが1つも無いレコードを定義することも出来る。

全てのフィールド(レコードコンポーネント)に値をセットするコンストラクターが自動的に生成されるので、自分でコンストラクターを定義する必要は無い。
特別なコンストラクターを定義したい場合だけ自分で記述する。

メソッドは自由に追加できるが、(static以外の)フィールドを追加することは出来ない。


レコード定義の構文は、Scalaのcase classの構文と似ている。
自動生成される内容はだいぶ違うけど…。


public record Point(int x, int y) { }

↓同等

public final class Point extends java.lang.Record {

	private final int x;

	private final int y;

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

	public int y() {
		return this.y;
	}
	@Override
	public int hashCode() {
		〜
	} 

	@Override
	public boolean equals(Object other) {
		〜
	} 

	@Override
	public String toString() {
		return "Point[x=" + this.x + ", y=" + this.y + "]";
	} 
}

recordで作られたクラスはjava.lang.Recordを継承している。
(自分で直接java.lang.Recordを継承したクラスを定義することは出来ない)

不変クラスなので、フィールドに値をセットするメソッドは無い。
値を取得するメソッドの名前はフィールド名と全く同じ。(getとかは付かない)
(つまり、JavaBeansとの互換性は無い)

hashCode・equals・toStringメソッドは自動生成される。
(実際は、生成されたclassファイルの中に直接コードがあるのではなく、invokedynamicを使って別の箇所を呼ぶようになっている。(実行時に生成するらしい?))


備考
record Point(int x, int y) {}

var p = new Point(1, 2);
System.out.println(p.x()); //→1
System.out.println(p.y()); //→2
レコードの値を取得する例。
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
System.out.println(p1 == p2);      //→false
System.out.println(p1.equals(p2)); //→true
レコードインスタンス同士を比較する例。
record Point() {} 値をひとつも定義しないレコードの例。[2020-09-22]
record Point {}

Point.java:1: エラー: レコード・ヘッダーが必要です
record Point {
^
値を定義する為の丸括弧を付けないと、コンパイルエラー。[2020-09-22]
record Example(int[] a) {
}
配列を指定する例。[2021-03-21]
なお、C言語形式の配列(普通は誰も使わない!)の「int a[]」でも指定できるが、正式版ではコンパイルエラーとなる。
record Range(int lo, int hi) {

  public Range {
    if(lo > hi) throw new IllegalArgumentException();
  }
}

new Range(1, 2); // OK
new Range(2, 1); // IllegalArgumentException
コンストラクターにチェック用の処理を追加する例。
コンストラクターの引数(丸括弧)を書かない状態で記述する。
(このコンストラクターは標準コンストラクター(canonical constructor)と言う)
ここでは、暗黙のコンストラクター引数が使用できる。
public record Range() {

  protected Range {
  }
}
Java14では、標準コンストラクターはpublic以外にすることは出来ない。[/2020-09-22]
Java15では、レコードクラスの可視性より強いアクセス権限は付けられない。
(例えば、publicなrecordにprotectedな標準コンストラクターは書けない)
record C(int n) {

  public C {
    this.n = 999;
  }
}
Java14では標準コンストラクター内で「this.」を付けてフィールドに値を設定できるが、
Java15ではコンパイルエラーになる。[2020-09-22]
record C(int n) {

  public C() {
    this(0);
  }
}

var c0 = new C();
var c1 = new C(1);
コンストラクターを追加する例。
record C(int n) {

  public C next() {
    return new C(n + 1);
  }
}

var c1 = new C(1);
var c2 = c1.next();
メソッドを追加する例。
record C(int x) {}
record D(int x, int y) {
  public static D create(C c, int y) {
    return new D(c.x(), y);
  }
}

var d = D.create(new C(1), 2);
staticメソッドを追加する例。
record RecordO(int n){

  @Override
  public int n() {
    return this.n + 1;
  }
}
アクセサーメソッドもオーバーライドすることが出来る。[2021-03-16]
ただし、Java14では@Overrideアノテーションを付けるとコンパイルエラーになる。
record F(int n) {
  private int m; // コンパイルエラー
}
フィールドを追加することは出来ない。
 
class Outer {
  class Inner {
    record C(int n) {}
  }
}
staticでないインナークラスの中でレコードを定義できない。[2020-09-22]
(正式版では定義できる。[2021-03-21]
record S(int n) implements Serializable {}

var bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(new S(123));
var bis = new ByteArrayInputStream(bos.toByteArray());
var s = new ObjectInputStream(bis).readObject();
インターフェースを実装する例。
Serializableをimplementsすると、シリアライズできる)
record C(int n) implements Cloneable {
  @Override
  public C clone() {
//  return (C)super.clone();
    return this;
  }
}

var c1 = new C(123);
var c2 = c1.clone();
インターフェースのメソッドを実装する例。
Cloneableをimplementsしてcloneメソッドをオーバーライドすると、クローンできる)
(レコードは不変オブジェクトなので、クローンで自分自身を返しても良い?
 まぁレコードでCloneableを実装することは無いだろうけど^^;)

リフレクション

リフレクション関連でもレコード関係のメソッドが追加されている。

備考
var c = Point.class;
System.out.println(c.isRecord()); //→true
クラスがレコードかどうか。
RecordComponent[] components = Point.class.getRecordComponents();
for (var rc : components) {
  System.out.println(rc); //→int x, int y
}
レコードコンポーネント(フィールド)一覧の取得。
レコードでない場合はnullが返る。
フィールドを持っていないレコードの場合は空配列が返る。
var rc = Point.class.getRecordComponents()[0];
System.out.println(rc.getName()); //→x
System.out.println(rc.getType()); //→int
System.out.println(rc.getDeclaringRecord()); //→class Point

var method = rc.getAccessor();
System.out.println(method.invoke(new Point(123, 456))); //→123
レコードコンポーネントからの情報取得。

プレビュー機能へ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま