enum と record を組み合わせることができないのはなぜですか? 質問する

enum と record を組み合わせることができないのはなぜですか? 質問する

最近、私は別のenum型を作成していました。Javaではenumは特別なタイプのクラス(名前付き整数定数(C# のように)。私は 2 つのフィールド、すべての引数を持つコンストラクター、および両方のフィールドのゲッターを使用してこれを作成しました。

これは例です:

enum NamedIdentity {

    JOHN(1, "John the Slayer"),
    JILL(2, "Jill the Archeress");

    int id;
    String nickname;

    NamedIdentity(int id, String nickname) {
        this.id = id;
        this.nickname = nickname;
    }

    id id() {
        return this.id;
    }

    String nickname() {
        return this.nickname;
    }
}

recordそこで、Java 14のキーワードを使えば、この機能に必要な定型コードを省くことができるのではないかと考えました。救おうとしていた私の知る限り、これはenumsと組み合わせられません。列挙レコードが存在していた場合、上記のコードは次のようになります。

enum record NamedIdentity(int id, String nickname) {
    JOHN(1, "John the Slayer"),
    JILL(2, "Jill the Archeress");
}

私の質問は、列挙レコード存在しないのですか? いくつか理由が考えられますが、以下に限定されるわけではありません:

  • この機能の使用例の数は少なすぎるため、Java 言語設計者として他の機能を設計および実装すれば、Java 言語はより多くのメリットを享受できるでしょう。
  • これは、列挙型の内部実装のため、実装が困難です。
  • Java 言語の設計者である私たちは、単にこれについて考えていなかったか、コミュニティからそのようなリクエストをまだ受け取っていなかったので、優先順位を付けませんでした。
  • この機能には意味上の問題があるか、この機能の実装によって意味上の曖昧さやその他の混乱が生じる可能性があります。

ベストアンサー1

要約

  • 技術的な制限: 多重継承によりスーパークラスEnumとの混在が防止されますRecord
  • 回避策:record列挙型のメンバフィールドとして保持する
NamedIdentity.JILL.detail.nickname()  // ➣ "Jill the Archeress"

多重継承

あなたはこう尋ねました:

enum レコードが存在しない理由はありますか?

技術的な理由は、Javaではすべての列挙型が暗黙的にサブクラスであるためです。Enumクラスですが、すべてのレコードは暗黙的にサブクラスですRecordクラス。

Java は多重継承をサポートしていません。そのため、この 2 つを組み合わせることはできません。

おそらく回避策が考案されたかもしれません。Javaチームはenumとrecordを組み合わせる機能を検討しましたが、何らかの理由でそれを却下しました。上記コメントによるブライアン・ゲッツJava言語アーキテクトオラクルにて。

セマンティクス

しかし、より重要なのは意味論です。

enum は、コンパイル時に名前付きインスタンスの限定セットを宣言するためのものです。enum クラスのロード時に、各 enum オブジェクトがインスタンス化されます。実行時には、そのクラスのオブジェクトをこれ以上インスタンス化することはできません (極端なリフレクション/イントロスペクション コードを使用すれば可能かもしれませんが、ここでは無視します)。

Java のレコードは自動的にインスタンス化されません。 を呼び出すことによって、コードはそのクラスのオブジェクトを必要な数だけインスタンス化しますnew。 したがって、まったく同じではありません。

定型句の削減はないの目標record

あなたが言った:

Java 14のrecordキーワードを使えば定型コードを省けると思ったのですが

record機能の目的を誤解しています。以下を読むことをお勧めします。JEP395、そしてこのテーマに関する Brian Goetz による最新のプレゼンテーションをご覧ください。

としてヨハネス・クーンによるコメントの目標recordない定型文を減らす。このような削減は嬉しい副作用ですが、 を発明した理由ではありませんrecord

レコードは、正式には「名目上のタプル」を意味します。

  • タプル特定の順序で並べられたさまざまなタイプの値のコレクション、または Wikipedia にあるように「要素の有限順序付きリスト (シーケンス)」を意味します。
  • 名目それぞれの要素に名前があることを意味します。

記録はシンプルで透明なデータキャリア透明は、そのすべてのメンバー フィールドが公開されることを意味します。そのゲッター メソッドは、JavaBeans の規則を使用せずに、フィールドと同じ名前が付けられますget…。 および のデフォルトの実装でhashCodeは、equalsすべてのメンバー フィールドが検査されます。レコードの目的は、動作 (メソッド) ではなく、運ばれるデータに焦点を当てることです。

さらに、記録は浅く不変不変レコードのインスタンス内のプリミティブ値やオブジェクト参照を変更できないことを意味します。レコードインスタンス内のオブジェクトはそれ自体が変更可能である可能性があり、これが浅くただし、レコード自体のフィールドの値 (プリミティブ値またはオブジェクト参照) は変更できません。レコードのメンバー フィールドの 1 つとして代替オブジェクトを再割り当てすることはできません。

  • コンパイル時に既知のインスタンスのセットが限られている場合は、 を使用しますenum
  • データ フィールドのグループを不変かつ透過的に運ぶことを主な仕事とするクラスを作成する場合は、 を使用しますrecord

価値のある質問

2 つの概念が交差する可能性があるのはわかります。コンパイル時に、名前付き値の不変の透過的なコレクションの限定されたセットがわかっているからです。したがって、あなたの質問は有効ですが、定型句の削減によるものではありません。ブライアン・ゲッツ氏は言うJava チームは実際にこのアイデアを検討しました。

回避策:recordenum

回避策は非常に簡単です:record列挙型のインスタンスを保持します。

レコードを列挙型コンストラクターに渡し、そのレコードを列挙型定義のメンバー フィールドとして保存できます。

メンバーフィールドを作成しますfinal。これで列挙型が作成されます不変したがって、プライベートとしてマークする必要はなく、ゲッター メソッドを追加する必要もありません。

まず、record定義です。

package org.example;

public record Performer(int id , String nickname)
{
}

次に、 のインスタンスをrecordenum コンストラクターに渡します。

package org.example;

public enum NamedIdentity
{
    JOHN( new Performer( 1 , "John the Slayer" ) ),
    JILL( new Performer( 2 , "Jill the Archeress" ) );

    final Performer performer;

    NamedIdentity ( final Performer performer ) { this.performer = performer; }
}

record列挙型のコンテキスト内でのみ意味がある場合は、別々の.javaファイルを持つのではなく、2 つをネストすることができます。このrecord機能はネストを考慮して構築されており、ネストでは適切に機能します。

ネストされた名前を付けるのrecordは難しい場合があります。Detailより良い名前がない場合は、次のような単純な汎用ラベルが適していると思います。

package org.example;

public enum NamedIdentity
{
    JOHN( new Performer( 1 , "John the Slayer" ) ),
    JILL( new Performer( 2 , "Jill the Archeress" ) );

    final Performer performer;

    NamedIdentity ( final Performer performer ) { this.performer = performer; }

    public record Performer(int id , String nickname) {}
}

私にとって、この回避策は確実な解決策です。定型句が減るという利点とともに、コードが明確になります。 を使用するとrecord意図が明確かつ明白になるため、列挙型に多数のデータ フィールドを保持する一般的な代替手段としてこれが気に入っています。あなたの質問に感謝します。今後の仕事でこれを使用する予定です。

そのコードを練習してみましょう。

for ( NamedIdentity namedIdentity : NamedIdentity.values() )
{
    System.out.println( "---------------------" );
    System.out.println( "enum name: " + namedIdentity.name() );
    System.out.println( "id: " + namedIdentity.performer.id() );
    System.out.println( "nickname: " + namedIdentity.performer.nickname() );
}
System.out.println( "---------------------" );

実行すると。

---------------------
enum name: JOHN
id: 1
nickname: John the Slayer
---------------------
enum name: JILL
id: 2
nickname: Jill the Archeress
---------------------

ローカルで宣言する

ちなみに、Java 16以降では、列挙型、レコード、インターフェースをローカルで宣言できます。これは、レコード機能を作成するために行われた作業の一環として実現しました。JEP 395: レコード

したがって、列挙型、レコード、およびインターフェースは、次の 3 つのレベルのいずれかで宣言できます。

  • 独自の.java.ファイル内。
  • クラス内にネストされます。
  • ローカルに、メソッド内で。

これについてはついでに言及しましたが、この質問とは直接関係ありません。

おすすめ記事