math
math

Reputation: 8850

Enum generated classes are Final or Not?

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

Answers (2)

Alexander Ivanchenko
Alexander Ivanchenko

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

Sweeper
Sweeper

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

Related Questions