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 p1 = new Point(1, 2); |
レコードインスタンス同士を比較する例。 |
record Point() {} |
値をひとつも定義しないレコードの例。 |
record Point {} ↓ Point.java:1: エラー: レコード・ヘッダーが必要です |
値を定義する為の丸括弧を付けないと、コンパイルエラーになる。 |
record Example(int[] a) { // OK [] ) { // NG |
配列を指定する例。 なお、C言語形式の配列(普通は誰も使わない!)「 int a[] 」を指定するとコンパイルエラーになる。 |
record Range(int lo, int hi) { |
コンストラクターにチェック用の処理を追加する例。 コンストラクターの引数(丸括弧)を書かない状態で記述する。 (このコンストラクターは標準コンストラクター(canonical constructor)と言う) ここでは、暗黙のコンストラクター引数が使用できる。 |
public record Range() { |
標準コンストラクターの可視性に、レコードクラスの可視性より強いアクセス権限は付けられない。 (例えば、publicなrecordにprotectedな標準コンストラクターは書けない) |
record C(int n) { |
標準コンストラクター内で「this. 」を付けてフィールドに値を設定すると、コンパイルエラーになる。(「 this. 」が付いていなければOK) |
record C(int n) { |
コンストラクターを追加する例。 |
record C(int n) { |
メソッドを追加する例。 |
record C(int x) {} |
staticメソッドを追加する例。 |
record RecordO(int n){ |
アクセサーメソッドもオーバーライドすることが出来る。 |
record F(int n) { |
フィールドを追加することは出来ない。 |
class Outer { |
インナークラスの中でもレコードを定義できる。 |
record S(int n) implements
Serializable {} |
インターフェースを実装する例。 (Serializableをimplementsすると、シリアライズできる) |
record C(int n) implements
Cloneable { |
インターフェースのメソッドを実装する例。 (Cloneableをimplementsしてcloneメソッドをオーバーライドすると、クローンできる) (レコードは不変オブジェクトなので、クローンで自分自身を返しても良い? まぁレコードでCloneableを実装することは無いだろうけど^^;) |
リフレクション関連でもレコード関係のメソッドが追加されている。
例 | 備考 |
---|---|
var c = Point.class; |
クラスがレコードかどうか。 |
RecordComponent[] components = Point.class.getRecordComponents(); |
レコードコンポーネント(フィールド)一覧の取得。 レコードでない場合はnullが返る。 フィールドを持っていないレコードの場合は空配列が返る。 |
var rc = Point.class.getRecordComponents()[0]; |
レコードコンポーネントからの情報取得。 |