Recently I was just creating another enum type. I took advantage of the fact that in Java, an enum is a special type of class (and not a named integer constant, like in C#). I made it with two fields, an all-arg constructor and getters for both fields.
This is an example:
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;
}
}
Then I thought that Java 14's record
keyword would have saved me the boilerplate code that this feature was trying to save me. This, as far as I know, doesn't combine with enum
s. If enum records would have existed, the abovementioned code would then look like this:
enum record NamedIdentity(int id, String nickname) {
JOHN(1, "John the Slayer"),
JILL(2, "Jill the Archeress");
}
My question is: is there a reason that enum records don't exist? I could imagine several reasons, including, but not limited to:
Enum and has several static members. Therefore enum cannot extend any other class or enum: there is no multiple inheritance. Class cannot extend enum as well. This limitation is enforced by compiler.
We cannot extend enum classes in Java. It is because all enums in Java are inherited from java.
The enum can be of any numeric data type such as byte, sbyte, short, ushort, int, uint, long, or ulong. However, an enum cannot be a string type.
Enums are lists of constants. When you need a predefined list of values which do represent some kind of numeric or textual data, you should use an enum. You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values.
This will actually throw an error in the second enum declaration if we are to leave it like this. The error reads: In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element. What this means is that, enum elements are automatically assigned values if not specified.
C++11 has introduced enum classes (also called scoped enumerations ), that makes enumerations both strongly typed and strongly scoped. Class enum doesn’t allow implicit conversion to int, and also doesn’t compare enumerators from different enumerations. To define enum class we use class keyword after enum keyword.
Class enum doesn’t allow implicit conversion to int, and also doesn’t compare enumerators from different enumerations. To define enum class we use class keyword after enum keyword. // Declaration enum class EnumName { Value1, Value2, ... ValueN}; // Initialisation EnumName ObjectName = EnumName::Value;
What this means is that, enum elements are automatically assigned values if not specified. So IT in the first Department enum is assigned the value 0 and Marketing is assigned the value of 1. In the second Department, since HR is the first element in that enum, it is also assigned the value of 0.
Enum
with Record
superclasses.record
as member field of your enumNamedIdentity.JILL.detail.nickname() // ➣ "Jill the Archeress"
You asked:
is there a reason that enum records don't exist?
The technical reason is that in Java, every enum is implicitly a subclass of Enum
class while every record is implicitly a subclass of Record
class.
Java does not support multiple inheritance. So the two cannot be combined.
But more important is the semantics.
An enum is for declaring at compile time a limited set of named instances. Each of the enum objects is instantiated when the enum class loads. At runtime, we cannot instantiate any more objects of hat class (well, maybe with extreme reflection/introspection code, but I’ll ignore that).
A record in Java is not automatically instantiated. Your code instantiated as many objects of that class as you want, by calling new
. So not at all the same.
record
You said:
I thought that Java 14's record keyword would have saved me the boilerplate code
You misunderstand the purpose of record
feature. I suggest you read JEP 395, and watch the latest presentation by Brian Goetz on the subject.
As commented by Johannes Kuhn, the goal of record
is not to reduce boilerplate. Such reduction is a pleasant side-effect, but is not the reason for the invention of record
.
A record is meant to be a “nominal tuple” in formal terms.
A record is meant to be a simple and transparent data carrier. Transparent means all its member fields are exposed. It’s getter methods are simply named the same as the field, without using the get…
convention of JavaBeans. The default implantation of hashCode
and equals
is to inspect each and every member field. The intention for a record is to focus on the data being carried, not behavior (methods).
Furthermore, a record is meant to be shallowly immutable. Immutable means you cannot change the primitive values, nor can you change the object references, in an instance of a record. The objects within a record instance may be mutable themselves, which is what we mean by shallowly. But values of the record’s own fields, either primitive values or object references, cannot be changed. You cannot re-assign a substitute object as one of the record’s member fields.
enum
.record
.I can see where the two concepts could intersect, where at compile time we know of a limited set of immutable transparent collections of named values. So your question is valid, but not because of boilerplate reduction. It would be interesting to ask Brian Goetz or others on the Java team if they ever discussed the notion.
record
on your enum
The workaround is quite simple: Keep a record
instance on your enum.
You could pass a record to your enum constructor, and store that record as a member field on the enum definition.
Make the member field final
. That makes our enum immutable. So, no need to mark private, and no need to add a getter method.
First, the record
definition.
package org.example;
public record Performer(int id , String nickname)
{
}
Next, we pass an instance of record
to the enum constructor.
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; }
}
If the record
only makes sense within the context of the enum, we can nest the two together rather than have separate .java
files. The record
feature was built with nesting in mind, and works well there.
Naming the nested record
might be tricky in some cases. I imagine something like Detail
might do as a plain generic label if no better name is obvious.
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) {}
}
To my mind, this workaround is a solid solution. We get clarity in the code with the bonus of reducing boilerplate. I like this as a general replacement for keeping a bunch of data fields on the enum, as using a record
makes the intention explicit and obvious. I expect to use this in my future work, thanks to your Question.
Let's exercise that code.
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( "---------------------" );
When run.
---------------------
enum name: JOHN
id: 1
nickname: John the Slayer
---------------------
enum name: JILL
id: 2
nickname: Jill the Archeress
---------------------
FYI, now in Java 16+, we can declare enums, records, and interfaces locally. This came about as part of the work done to create the records feature. See JEP 395: Records.
So enums, records, and interfaces can be declared at any of three levels:
.java.
file.I mention this in passing, not directly relevant to this Question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With