Reputation: 8850
When I specify an enum like this:
enum Color { RED }
The generated code javap -p Color
looks like this:
final class Color extends java.lang.Enum<Color> {
public static final Color RED;
private static final Color[] $VALUES;
public static Color[] values();
public static Color valueOf(java.lang.String);
private Color();
private static Color[] $values();
static {};
}
However, when I add custom extensions like an explicit constructor, e.g.:
enum Color {
RED,
Color() {}
}
I get this:
class Color extends java.lang.Enum<Color> {
public static final Color RED;
public static final Color Color;
private static final Color[] $VALUES;
public static Color[] values();
public static Color valueOf(java.lang.String);
private Color();
private static Color[] $values();
static {};
}
Missing the final
modifier for generated class Color. However, I cannot extend Color
(as usual):
final class B extends Color {}
The compiler gives:
error: enum types are not extensible
final class B extends Color {}
^
1 error
So why is the final
modifier missing when adding custom code to the enum class? I suppose the prevention of subclassing Color anyway is performed by a sanity check from the compiler (aka: if super class typeof enum => deny), right?
I am using OpenJDK 17.0.2
Upvotes: 6
Views: 489
Reputation: 29028
Explicit final
modifier isn't allowed with enum classes, because in fact they are not final.
That allows to "extend an enum class internally" by creating subclasses representing its enum constants under the hood. And these sublasses in turn are final
.
According to the Java language specification:
The optional class body of an enum constant implicitly declares an anonymous class (§15.9.5) that (i) is a direct subclass of the immediately enclosing enum class (§8.1.4), and (ii) is final (§8.1.1.2). The class body is governed by the usual rules of anonymous classes;
I.e. every enum constant can provide an optional class-body enclosed in curly braces CONSTANT_NAME{ body }
. Which in fact is an implicit declaration of an anonymous inner class which extends the enclosing enum class. And as with a usual anonymous class, it's possible to override the behavior of the parent class.
Also note that because as a matter of fact every enum
is extendible (but not in the same way as a regular class can be extended), it's possible to declare abstract
methods within an enum
. If there's at least one such a method, it needs to be overridden by the anonymous class of every enum constant, i.e. every enum-member has to declare an optional class body for that.
Let's explore it with an example:
class EnumTest {
public enum StringProcessor {
UPPER {
@Override
public String process(String str) {
return str.toUpperCase();
}
},
LOWER {
@Override
public String process(String str) {
return str.toLowerCase();
}
},
REMOVE_DIGITS {
@Override
public String process(String str) {
return str.replaceAll("\\d+", "");
}
};
public abstract String process(String str);
}
public static void main(String[] args) {
System.out.println(StringProcessor.REMOVE_DIGITS.process("27test_string"));
System.out.println("Enclosing enum :\t" + StringProcessor.class);
System.out.println("Enclosing UPPER :\t" + StringProcessor.UPPER.getClass());
System.out.println("Enclosing REMOVE_DIGITS :\t" + StringProcessor.REMOVE_DIGITS.getClass());
System.out.println("classes of enum members are the same : " + (StringProcessor.UPPER.getClass() == StringProcessor.REMOVE_DIGITS.getClass()));
}
}
Will give the output:
test_string
Enclosing enum : class _classpath_.EnumTest$StringProcessor
Enclosing UPPER : class _QUESTIONS.ClassDisign.EnumTest$StringProcessor$1
Enclosing REMOVE_DIGITS : class _classpath_.EnumTest$StringProcessor$3
classes of enum members are the same : false
If it was allowed to declare enum
as final explicitly, the mechanism you see above will not work.
And at the same time it is for forbidden to extend enum
class, although it's technically not final because enum
is meant to represent a set of well define elements and base enum has to be aware of all of them.
Upvotes: 1
Reputation: 272750
You did not add an explicit constructor here:
enum Color {
RED,
Color() {}
}
Instead, you added another enum constant, called Color
, with an empty class body. This is evident in the unexpected line in the javap
output:
public static final Color Color;
Therefore, an (empty) anonymous class has to be created, that inherits from the enum class, to serve as the class for the enum constant "Color
". Therefore, the enum Color
cannot be final. You should see an extra Color$1.class
in the javac output folder.
Whether an enum should be final is stated in the Java Language Specification as follows:
An enum class is either implicitly final or implicitly sealed, as follows:
- An enum class is implicitly final if its declaration contains no enum constants that have a class body (§8.9.1).
- [...]
The enum Color
has the enum constant Color
that has a body, so it is not final
.
The correct way to add a constructor to an enum is to separate it from the constants using a ;
. ,
is used to separate constants. Example:
enum Color {
RED, GREEN, BLUE;
Color() {}
}
And as expected, this Color
enum is final
.
Upvotes: 7