S-JIS[2020-03-21/2021-03-21] 変更履歴
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 p1 = new Point(1, 2); |
レコードインスタンス同士を比較する例。 |
record Point() {} |
値をひとつも定義しないレコードの例。[2020-09-22] |
record Point {} ↓ Point.java:1: エラー: レコード・ヘッダーが必要です |
値を定義する為の丸括弧を付けないと、コンパイルエラー。[2020-09-22] |
record Example(int[] a) { |
配列を指定する例。[2021-03-21] なお、C言語形式の配列(普通は誰も使わない!)の「 int a[] 」でも指定できるが、正式版ではコンパイルエラーとなる。 |
record Range(int lo, int hi) { |
コンストラクターにチェック用の処理を追加する例。 コンストラクターの引数(丸括弧)を書かない状態で記述する。 (このコンストラクターは標準コンストラクター(canonical constructor)と言う) ここでは、暗黙のコンストラクター引数が使用できる。 |
public record Range() { |
Java14では、標準コンストラクターはpublic以外にすることは出来ない。[/2020-09-22] Java15では、レコードクラスの可視性より強いアクセス権限は付けられない。 (例えば、publicなrecordにprotectedな標準コンストラクターは書けない) |
record C(int n) { |
Java14では標準コンストラクター内で「this. 」を付けてフィールドに値を設定できるが、Java15ではコンパイルエラーになる。[2020-09-22] |
record C(int n) { |
コンストラクターを追加する例。 |
record C(int n) { |
メソッドを追加する例。 |
record C(int x) {} |
staticメソッドを追加する例。 |
record RecordO(int n){ |
アクセサーメソッドもオーバーライドすることが出来る。[2021-03-16] ただし、Java14では@Overrideアノテーションを付けるとコンパイルエラーになる。 |
record F(int n) { |
フィールドを追加することは出来ない。 |
class Outer { |
staticでないインナークラスの中でレコードを定義できない。[2020-09-22] (正式版では定義できる。[2021-03-21]) |
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]; |
レコードコンポーネントからの情報取得。 |