为什么枚举和记录不能合并?tl;博士多重继承语义学样板减少不是目标record 值得一提的问题解决办法:将 a 存储在您的recordenum 本地声明

2022-09-04 02:58:15

最近,我只是在创建另一个枚举类型。我利用了这样一个事实,即在Java中,枚举是一种特殊类型的类(而不是像C#中那样的命名整数常量)。我用两个字段做了它,一个全arg构造函数和两个字段的getter。

下面是一个示例:

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;
    }
}

然后我认为Java 14的关键字会为我节省这个功能试图拯救我的样板代码。据我所知,这与s没有结合。如果枚举记录s 存在,则上述代码将如下所示:recordenum

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

我的问题是:枚举记录不存在是有原因的吗?我可以想象几个原因,包括但不限于:

  • 这个特性的用例数量太少了,如果我们作为Java语言设计师,设计和实现其他特性,Java语言将受益更多。
  • 由于枚举类型的内部实现,这很难实现。
  • 作为Java语言设计师,我们根本没有想到这一点,或者我们还没有收到来自社区的这样的请求,所以我们没有优先考虑它。
  • 此功能将存在语义问题,或者此功能的实现可能会导致语义不明确或其他混淆。

答案 1

tl;博士

  • 技术限制:多重继承可防止与超类混合。EnumRecord
  • 解决办法:保留枚举的“作为成员”字段record
NamedIdentity.JILL.detail.nickname()  // ➣ "Jill the Archeress"

多重继承

你问:

枚举记录不存在是有原因的吗?

技术原因是,在Java中,每个枚举都是Enum类的子类,而每个记录都是Record类的子类。

Java 不支持多重继承。所以两者不能结合起来。

语义学

但更重要的是语义。

枚举用于在编译时声明一组有限的命名实例。每个枚举对象在枚举类加载时实例化。在运行时,我们无法实例化任何更多的 hat 类对象(好吧,也许使用极端反射/内省代码,但我会忽略它)。

Java 中的记录不会自动实例化。您的代码通过调用 来实例化该类的任意多个对象。所以完全不一样。new

样板减少不是目标record

你说:

我以为Java 14的记录关键字会为我节省样板代码

您误解了功能的目的。我建议你阅读JEP 395,并观看Brian Goetz关于这个主题的最新演讲。record

正如Johannes Kuhn所评论的那样,其目标不是减少样板。这种减少是一种令人愉快的副作用,但不是发明的原因。recordrecord

记录在形式上意味着“名义元组”。

  • 元组是指按特定顺序排列的各种类型值的集合,或者如维基百科所说:“元素的有限有序列表(序列)”。
  • 名义表示每个元素都有一个名称。

记录意味着是一个简单而透明的数据载体透明表示其所有成员字段都已公开。它的 getter 方法只是简单地命名为与字段相同的方法,而不使用 JavaBeans 的约定。的默认植入是检查每个成员字段。记录的目的是关注正在携带的数据,而不是行为(方法)。get…hashCodeequals

此外,记录意味着浅层不可变不可变意味着您无法更改记录实例中的基元值,也无法更改对象引用。记录实例中的对象本身可能是可变的,这就是我们所说的浅层。但是,无法更改记录本身字段的值(基元值或对象引用)。不能将替换对象重新指定为记录的成员字段之一。

  • 如果在编译时已知有一组有限的实例,请使用 。enum
  • 当您编写一个类时,其主要工作是以不可变且透明的方式携带一组数据字段,请使用 。record

值得一提的问题

我可以看到这两个概念在哪里可以相交,在编译时我们知道一组有限的不可变的透明命名值集合。所以你的问题是有效的,但不是因为样板减少。询问Brian Goetz或Java团队中的其他人是否曾经讨论过这个概念会很有趣。

解决办法:将 a 存储在您的recordenum

解决方法非常简单:在枚举中保留一个记录实例。

您可以将记录传递给枚举构造函数,并将该记录存储为枚举定义上的成员字段。

将“成员”字段设为 。这使得我们的枚举不可变。因此,无需标记私有,也无需添加 getter 方法。final

首先是定义。record

package org.example;

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

接下来,我们将 的实例传递给枚举构造函数。record

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.javarecord

在某些情况下,命名嵌套可能很棘手。我想如果没有更好的名字是显而易见的,像这样的东西可能会作为一个普通的通用标签。recordDetail

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) {}
}

在我看来,这种解决方法是一个可靠的解决方案。我们通过减少样板的优点使代码变得清晰。我喜欢将其作为在枚举上保留一堆数据字段的一般替代,因为使用a会使意图明确而明显。我希望在我未来的工作中使用它,感谢你的问题。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:记录

因此,枚举、记录和接口可以在以下三个级别中的任何一个级别声明:

  • 在他们自己的文件中。.java.
  • 嵌套在类中。
  • 在方法中本地。

我顺便提到这一点,与这个问题没有直接关系。


答案 2

你为什么不连接龙目岛?要删除的额外代码将随着批注@AllArgsConstructor消失,并@Getters