为什么enum和record不能合并?
Why can't enum and record be combined?
最近我正在创建另一个枚举类型。我利用了以下事实:在 Java 中,枚举是 class 的一种特殊类型(而不是像 C# 中那样的 named integer constant)。我用两个字段创建了它,一个全参数构造函数和两个字段的吸气剂。
这是一个例子:
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");
}
我的问题是:是否存在 枚举记录 不存在的原因?我可以想象出几个原因,包括但不限于:
- 此功能的用例数量太少,如果我们作为 Java 语言设计者设计和实现其他功能,Java 语言将受益更多。
- 由于枚举类型的内部实现,这很难实现。
- 作为Java语言设计者,我们根本没有考虑到这一点,或者我们还没有收到社区的这样的要求,所以我们没有优先考虑。
- 此功能可能存在语义问题,或者此功能的实现可能会导致语义歧义或其他混淆。
tl;博士
- 技术限制:多重继承防止混合
Enum
与 Record
superclasses.
- 解决方法:将
record
保留为枚举的成员字段
NamedIdentity.JILL.detail.nickname() // ➣ "Jill the Archeress"
多重继承
您问的是:
is there a reason that enum records don't exist?
技术原因是在 Java 中,每个枚举都隐含地是 Enum
class while every record is implicitly a subclass of Record
class.
的子 class
Java不支持多重继承。所以两者不能结合。
语义
但更重要的是语义。
枚举用于在编译时声明一组有限的命名实例。当枚举 class 加载时,每个枚举对象都会被实例化。在 运行 时,我们无法再实例化帽子 class 的任何对象(好吧,也许使用极端的 reflection/introspection 代码,但我会忽略它)。
Java 中的记录不会自动实例化。您的代码通过调用 new
实例化了 class 的尽可能多的对象。所以完全不一样。
减少样板代码 不是 record
的目标
你说:
I thought that Java 14's record keyword would have saved me the boilerplate code
您误解了 record
功能的用途。我建议您阅读 JEP 395,并观看 Brian Goetz 关于该主题的最新演讲。
与一样,record
的目标是而不是减少样板文件。这种减少是一个令人愉快的副作用,但不是发明 record
.
的原因
记录在形式上是一个“名义元组”。
- 元组表示按一定顺序排列的各种类型值的集合,或者如维基百科所说:“元素的有限有序列表(序列)”。
- Nominal表示每个元素都有一个名称。
记录本应是一种简单且透明的数据载体。 Transparent表示它的所有成员字段都暴露了。它的 getter 方法只是简单地命名为与字段相同,没有使用 JavaBeans 的 get…
约定。 hashCode
和 equals
的默认植入是检查每个成员字段。记录的目的是关注所携带的数据,而不是行为(方法)。
此外,记录应该是浅度不可变的。 不可变 意味着您不能更改原始值,也不能更改记录实例中的对象引用。记录实例中的对象本身可能是可变的,这就是我们所说的 shallowly 的意思。但是记录自身字段的值,无论是原始值还是对象引用,都无法更改。您不能将替代对象重新分配为记录的成员字段之一。
- 如果编译时已知的实例集有限,请使用
enum
。
- 当你编写一个 class 的主要工作是不可变且透明地携带一组数据字段时,请使用
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.
文件中。
- 嵌套在 class.
中
- 本地,在一个方法内。
我顺便提一下,与这个问题没有直接关系。
最近我正在创建另一个枚举类型。我利用了以下事实:在 Java 中,枚举是 class 的一种特殊类型(而不是像 C# 中那样的 named integer constant)。我用两个字段创建了它,一个全参数构造函数和两个字段的吸气剂。
这是一个例子:
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");
}
我的问题是:是否存在 枚举记录 不存在的原因?我可以想象出几个原因,包括但不限于:
- 此功能的用例数量太少,如果我们作为 Java 语言设计者设计和实现其他功能,Java 语言将受益更多。
- 由于枚举类型的内部实现,这很难实现。
- 作为Java语言设计者,我们根本没有考虑到这一点,或者我们还没有收到社区的这样的要求,所以我们没有优先考虑。
- 此功能可能存在语义问题,或者此功能的实现可能会导致语义歧义或其他混淆。
tl;博士
- 技术限制:多重继承防止混合
Enum
与Record
superclasses. - 解决方法:将
record
保留为枚举的成员字段
NamedIdentity.JILL.detail.nickname() // ➣ "Jill the Archeress"
多重继承
您问的是:
is there a reason that enum records don't exist?
技术原因是在 Java 中,每个枚举都隐含地是 Enum
class while every record is implicitly a subclass of Record
class.
Java不支持多重继承。所以两者不能结合。
语义
但更重要的是语义。
枚举用于在编译时声明一组有限的命名实例。当枚举 class 加载时,每个枚举对象都会被实例化。在 运行 时,我们无法再实例化帽子 class 的任何对象(好吧,也许使用极端的 reflection/introspection 代码,但我会忽略它)。
Java 中的记录不会自动实例化。您的代码通过调用 new
实例化了 class 的尽可能多的对象。所以完全不一样。
减少样板代码 不是 record
的目标
你说:
I thought that Java 14's record keyword would have saved me the boilerplate code
您误解了 record
功能的用途。我建议您阅读 JEP 395,并观看 Brian Goetz 关于该主题的最新演讲。
与record
的目标是而不是减少样板文件。这种减少是一个令人愉快的副作用,但不是发明 record
.
记录在形式上是一个“名义元组”。
- 元组表示按一定顺序排列的各种类型值的集合,或者如维基百科所说:“元素的有限有序列表(序列)”。
- Nominal表示每个元素都有一个名称。
记录本应是一种简单且透明的数据载体。 Transparent表示它的所有成员字段都暴露了。它的 getter 方法只是简单地命名为与字段相同,没有使用 JavaBeans 的 get…
约定。 hashCode
和 equals
的默认植入是检查每个成员字段。记录的目的是关注所携带的数据,而不是行为(方法)。
此外,记录应该是浅度不可变的。 不可变 意味着您不能更改原始值,也不能更改记录实例中的对象引用。记录实例中的对象本身可能是可变的,这就是我们所说的 shallowly 的意思。但是记录自身字段的值,无论是原始值还是对象引用,都无法更改。您不能将替代对象重新分配为记录的成员字段之一。
- 如果编译时已知的实例集有限,请使用
enum
。 - 当你编写一个 class 的主要工作是不可变且透明地携带一组数据字段时,请使用
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.
文件中。 - 嵌套在 class. 中
- 本地,在一个方法内。
我顺便提一下,与这个问题没有直接关系。