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

レコード

Javaのレコードについて。


概要

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

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


レコード定義方法

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

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

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

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


定義したレコードは、クラスと同様にnewでインスタンスを生成する。

var 変数 = new レコード名(値, …);

レコード定義の構文は、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との互換性は無い)
アクセサーメソッド(accessor)と呼ぶらしい。

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() {} 値をひとつも定義しないレコードの例。
record Point {}

Point.java:1: エラー: レコード・ヘッダーが必要です
record Point {
^
値を定義する為の丸括弧を付けないと、コンパイルエラーになる。
record Example(int[] a) { // OK
}

record Example(int a
[]) { // NG
}
配列を指定する例。
なお、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 {
  }
}
標準コンストラクターの可視性に、レコードクラスの可視性より強いアクセス権限は付けられない。
(例えば、publicなrecordにprotectedな標準コンストラクターは書けない)
record C(int n) {

  public C {
    this.n = 999; // NG
    n = 999; // OK
  }
}
標準コンストラクター内で「this.」を付けてフィールドに値を設定すると、コンパイルエラーになる。
(「this.」が付いていなければOK)
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;
  }
}
アクセサーメソッドもオーバーライドすることが出来る。
record F(int n) {
  private int m; // コンパイルエラー
}
フィールドを追加することは出来ない。
 
class Outer {
  class Inner {
    record C(int n) {}
  }
}
インナークラスの中でもレコードを定義できる。
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目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま