java 为什么不能合并枚举和记录?

rvpgvaaj  于 2022-12-10  发布在  Java
关注(0)|答案(2)|浏览(175)

最近我创建了另一个枚举类型。我利用了在Java中枚举是一种特殊类型的类(而不是像C#中的named integer constant)这一事实。我用两个字段创建了它,一个全参数构造函数和两个字段的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的record关键字可以为我节省这个特性was trying to save的样板代码。据我所知,这并不与enum s合并。如果 enum records存在,上述代码将如下所示:

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

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

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

lsmepo6l1#

tl; d天

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

多重继承

你问:
枚举记录不存在有什么原因吗?
技术原因是在Java中,每个枚举都隐式地是Enum类的子类,而每个记录都隐式地是Record类的子类。
Java不支持多重继承,所以两者不能结合。

语义

但更重要的是语义。
枚举是在编译时声明一组有限的命名示例。当枚举类加载时,每个枚举对象都被示例化。在运行时,我们不能示例化更多的hat类对象(好吧,可能需要极端的反射/内省代码,但我会忽略它)。
Java中的记录不会自动示例化,你的代码可以通过调用new来示例化该类的任意多个对象,所以完全不一样。

减少样板 * 不是 * record的目标

你说:
我以为Java 14的record关键字可以节省样板代码,
您误解了record特性的目的。我建议您阅读JEP 395,并观看Brian Goetz关于该主题的最新演示。
正如Johannes Kuhn所评论的,record的目标 * 不是 * 减少样板文件。这种减少是一个令人愉快的副作用,但不是发明record的原因。
记录在形式上是一个“名义元组”。

  • Tuple 表示按一定顺序排列的各种类型的值的集合,或者如Wikipedia所述:“元素的有限有序列表(序列)”。
  • Nominal 表示每个元素都有一个名称。

记录应该是一个简单的、透明的数据载体。* 透明 * 意味着它的所有成员字段都是公开的。它的getter方法的名称与字段相同,hashCodeequals的默认移植是检查每个成员字段。记录的目的是关注所携带的数据,而不是行为(方法)。
此外,记录是浅不可变的。* 不可变 * 意味着你不能改变记录示例中的原始值,也不能改变对象引用。记录示例中的对象本身可能是可变的,这就是我们所说的 * 浅 *。但是记录自身字段的值,无论是原始值还是对象引用,不能更改。您不能将替代对象重新分配为记录的成员字段之一。

  • 如果在编译时已知的示例集有限,请使用enum
  • 当您编写的类的主要任务是不可变地、透明地携带一组数据字段时,请使用record

值得一提的问题
我可以看到这两个概念在哪里交叉,在编译时我们知道有限的一组不可变的透明命名值集合。所以你的问题是有效的,但不是因为样板文件的减少。Brian Goetz说Java团队确实考虑过这个想法。

解决方法:在您的enum上存储record

解决方法很简单:在枚举上保留record执行严修。
您可以将数据录传递至枚举建构函式,并将该数据录储存为枚举定义上的成员字段。
将成员字段设为final。这将使我们的枚举为immutable。因此,不需要将其标记为private,也不需要添加getter方法。
首先,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只在枚举的上下文中有意义,我们可以将两者嵌套在一起,而不是使用单独的.java文件。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: Records
因此,枚举、记录和接口可以在以下三个级别中的任何一个级别上声明:

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

我只是顺便提一下,与这个问题没有直接关系。

xtfmy6hx

xtfmy6hx2#

为什么不连接Lombok呢?要删除的额外代码将与注解@AllArgsConstructor和@Getters一起消失

相关问题