【问题标题】:Why can't enum and record be combined?为什么不能将枚举和记录结合起来?
【发布时间】:2021-05-05 19:20:37
【问题描述】:

最近我只是在创建另一个枚举类型。我利用了这样一个事实,即在 Java 中,枚举是一种特殊类型的类(而不是 named integer constant,就像在 C# 中那样)。我使用了两个字段,一个全参数构造函数和两个字段的 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 我的样板代码。据我所知,这不与enums 结合使用。如果 enum records 已经存在,那么上述代码将如下所示:

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

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

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

【问题讨论】:

  • 那么 NamedIdentity 是否应该隐式扩展 java.lang.Enumjava.lang.Record
  • 记录与样板减少无关。事实上,这种记录的一个属性是以下代码返回 true:original.equals(new NamedIdentitiy(original.id(), original.nickname()))。由于枚举没有公共构造函数,所以这段代码不能工作。
  • 当您可以将NamedIdentity 定义为记录并在需要它们的地方声明static final NamedIdentity 字段时,为什么还要使用枚举?
  • @ayane 因为你可以切换枚举。
  • 我可以向你保证,答案不是“我们没有考虑过这个。”

标签: java enums java-record


【解决方案1】:

tl;博士

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

多重继承

你问:

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

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

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

语义

但更重要的是语义。

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

Java 中的记录不会自动实例化。您的代码通过调用new 实例化了该类的任意数量的对象。所以根本不一样。

减少样板代码不是record 的目标

你说:

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

您误解了record 功能的用途。我建议您阅读JEP 395,并观看 Brian Goetz 关于该主题的最新演示。

作为commented by Johannes Kuhnrecord 的目标不是减少样板。这种减少是一个令人愉快的副作用,但不是record发明的原因。

一条记录在形式上是一个“名义元组”。

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

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

此外,记录应该是浅不可变的不可变意味着您不能更改原始值,也不能更改记录实例中的对象引用。记录实例中的对象本身可能是可变的,这就是我们所说的。但是记录自身字段的值,无论是原始值还是对象引用,都不能更改。您不能将替代对象重新分配为记录的成员字段之一。

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

有价值的问题

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

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

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

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

将成员字段设为final。这使得我们的枚举immutable。所以,不需要标记私有,也不需要添加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 的未来版本中看到 enum record 语法仍然会很有趣。当然,这实际上可能只是您建议的解决方案的语法糖——一个嵌套的记录类,Java 编译器生成构造函数和访问方法,所有这些都委托给“不可见”的私有记录字段。这样我们就不必在每个项目上都做new Details(,或者在任何地方手动访问.details
  • 关于多重继承这一点——我认为说“它们不能存在,因为在 Java 中不可能实现多重继承”并不是一个强有力的论据。如果他们真的想让枚举和记录结合起来,他们可以走另一条路,而不是从某个超类扩展。例如,我看不出Record 类型不应该是接口的真正原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 2014-03-21
  • 1970-01-01
  • 2013-07-22
  • 2019-03-24
  • 2011-07-06
相关资源
最近更新 更多