PROPOSAL: abstract enums

Derek Foster vapor1 at teleport.com
Sun Mar 29 20:13:45 PDT 2009


AUTHOR: Derek Foster

OVERVIEW

The Java Enumerated type feature is unlike similar features in any other language which preceded it. In prior languages, an enumerated type was simply a collection of integer constants. In contrast, Java's enumerated type is a rich datatype that has more in common with classes than it has with a simple constants. Enumerated types can contain complex data, as well as methods to operate on that data. Individual enumerators may be subclasses of the enumeration class that contains them, and may override methods to provide enumerator-specific behavior.

Unfortunately, there is one striking difference between Java's enumerations and classes that can become a significant inconvenience: While classes allow use of a superclass to hold data and methods which are shared between a variety of subclasses, Java enumerations have no such feature. In particular, every Java enumerated type must explicitly specify all of its data and methods that are not inherited from the base class Enum<T>. If there are a number of enumerations that share related behavior, each enumeration must specify it redundantly. The only way to share behavior between these enumerations is to manually implement use delegation.

For instance, imagine a system in which enumerations are used to access columns of tables in a relational database. There might be one enumeration for each table, and the enumerators of each type might represent the columns in that table. Presumably, each enumerator would contain the name of the table, as well as the name of a specific column, and perhaps other metadata, such as access privileges.

It would seem natural to model such a system like this:

enum Access { PUBLIC, PRIVATE };

/** 
 * This holds the shared definition of what it means
 * to be a database table column identifier 
 */
abstract enum TableColumnDef {
    private String tableName;
    private String columnName;
    private Access access;
    protected DatabaseEnum(String tablename, String columnName, Access access) {
        this.tableName = tableName;
        this.columnName = columnName;
        this.access = access;
    }
    public String getTableName() {
        return tableName;
    }
    public String getColumnName() {
        return columnName;
    }
    public Access getAccess() {
        return access;
    }
    @Override
    final String toString() {
        return tableName + "." + columnName;
    }
}

/** A specific table */
enum PersonTable extends TableColumnDef {
    NAME_COLUMN("Name", Access.PUBLIC),
    AGE_COLUMN("Age", Access.PRIVATE),
    WEIGHT_COLUMN("Weight", ACCESS.PUBLIC);
    private PersonTable(String columnName, Access access) {
         super("PersonTable", columnName, access);
    }
}

/** Another specific table */
enum PetTable extends TableColumnDef {
    PETNAME_COLUMN("PetName", Access.PUBLIC),
    SPECIES_COLUMN("Species", Access.PUBLIC);
    private PersonTable(String columnName, Access access) {
         super("PetTable", columnName, access);
    }
}

However, this cannot be done in Java, because inheritance is not supported for enumerated types. The closest equivalent is to model the system something like this, using delegation instead of inheritance:

class TableEnumGuts {
    private String tableName;
    private String columnName;
    private Access access;
    protected DatabaseEnum(String tablename, String columnName, Access access) {
        this.tableName = tableName;
        this.columnName = columnName;
        this.access = access;
    }
    public String getTableName() {
        return tableName;
    }
    public String getColumnName() {
        return columnName;
    }
    public Access getAccess() {
        return access;
    }
    @Override
    final String toString() {
        return tableName + "." + columnName;
    }
}

enum PersonTable {
    NAME_COLUMN("Name"),
    AGE_COLUMN("Age"),
    WEIGHT_COLUMN("Weight");
    private TableEnumGuts guts;
    private PersonTable(String columnName, Access access) {
        guts = new TableEnumGuts("PersonTable", columnName, access);
    }
    public String getTableName() {
        return guts.getTableName();
    }
    public String getColumnName() {
        return guts.getColumnName();
    }
    public Access getAccess() {
        return guts.getAccess();
    }
    @Override
    final String toString() {
        return guts.toString();
    }
}

enum PetTable extends TableEnum {
    PETNAME_COLUMN("PetName"),
    SPECIES_COLUMN("Species");
    private TableEnumGuts guts;
    private PersonTable(String columnName) {
        guts = new TableEnumGuts("PersonTable", columnName, access);
    }
    public String getTableName() {
        return guts.getTableName();
    }
    public String getColumnName() {
        return guts.getColumnName();
    }
    public Access getAccess() {
        return guts.getAccess();
    }
    @Override
    final String toString() {
        return guts.toString();
    }
}

Note how much more code (and worst of all, boilerplate duplicate code) is required in the second example, since each enumerated type must provide forwarding methods for each method in the 'TableEnumGuts' class.

In one example that the author of this proposal was forced to deal with, there were 30 such enumerated types which conceptually extended the same base class, each of which had to implement that conceptual inheritance by delegating four or so methods to an internal "guts" object. This meant that any time time the signature of any of those four methods had to be changed, the change had to take place in 30 different forwarding methods. In another case (working for a different company) there were 10 such methods, duplicated over four conceptual subclasses, with similar problems.

The alternative to delegation is, of course, to duplicate code in each enumerated type. This sort of "cut and paste programming" introduces its own maintenance problems.

This proposal attempts to define the semantics of abstract enumerated types, and what it means for an enumerated type to have a supertype, so as to allow code similar to the first example to work as expected in a Java compiler.


FEATURE SUMMARY:

Defines the meaning of inheritance from abstract supertypes for enumerated types.

Note that this proposal specifically does not allow abstract enumerated types to have enumerators, since the semantics of these create difficult problems with inheritance (and open, in the words of one evaluator to a Sun bug submission, "a can of worms").


MAJOR ADVANTAGE:

Eliminates a missing feature between java enumerations and java classes, and restores a feature of the "typesafe enumeration" pattern that is not supported by Java enumerations as they are currently implemented.


MAJOR BENEFIT:

Elimination of duplicate code when related enumerated types need to be declared. Allows subclassing within enumeration types. Eases maintenance of families of enumerated types.


MAJOR DISADVANTAGE:

There will be some work in the compiler necessary to implement this feature.


ALTERNATIVES:

Note the delegation workaround being used above. This allows subtypes to share code, but results in potentially large numbers of forwarding methods and difficulties with code maintenance.


EXAMPLES

SIMPLE EXAMPLE:

abstract enum SuperEnum {
    int getTheAnswerToTheUltimateQuestion() {
         return 42;
    }
}

enum MyEnum extends SuperEnum {
     MY_ENUMERATOR;
}

class DeepThought {
    Object returnTheAnswer() {
        return MY_ENUMERATOR.getTheAnswerToTheUltimateQuestion();
    }
}


ADVANCED EXAMPLE:

abstract enum Base1 {
    protected final int sharedData;
    public int getSharedData() {return sharedData;}
    protected Base1(int sharedData) {
        this.sharedData = sharedData;
    }
}

abstract enum Base2 extends Base1 {
    protected final int otherSharedData;
    public int getOtherSharedData() {return sharedData;}
    protected Base2(int sharedData, int otherSharedData) {
        super(sharedData);
        this.otherSharedData = sharedData;
    }
}

enum Instances extends Base2 {
    INSTANCE1(10, 100),
    INSTANCE2(20, 200),
    INSTANCE3(30, 300) {
         public int getSharedData() { return 40; }
    }
    private Instances(int sharedData, int otherSharedData) {
        super(sharedData, otherSharedData);
    }
}

class AClient {
    void someFunc() {
        assert INSTANCE1.getSharedData()==10;
        assert INSTANCE2.getSharedData()==20;
        assert INSTANCE3.getSharedData()==40;
        Instances[] values = instances.values(); // OK
        Instances value = Instances.valueOf("INSTANCE1")); // OK
        Base2[] values2 = Base2.values(); // COMPILER ERROR: No such method.
        Base2 value2 = Base2.valueOf("INSTANCE1"); // COMPILER ERROR: No such method.
    }
}

DETAILS

SPECIFICATION:

A concept of an "abstract enum" shall be added to the Java language, denoted by prepending the word "abstract" to an enumerated type declaration, with restrictions as described below.

Rule 1: An enumerated type (abstract or not) shall either declare no supertype, or shall declare a supertype which is an abstract enumerated type.

Rule 2: It shall be a compile-time error for an ordinary class to extend an enumerated type, either abstract or not.

Rule 3: Both abstract and non-abstract enumerated types implicitly have a supertype (not necessarily a direct supertype) of Enum<E extends Enum<E>>. Thus, within the body of an abstract enumerated type, access to methods of this supertype (ordinal(), name(), etc.) is allowed.

Rule 4: Abstract enumerated types shall not declare enumerators. The body of an abstract enumerated type shall be syntactically and semantically the same as that of an ordinary abstract class which extends its declared supertype (as per the COMPILATION section below), except with the restriction that all constructors must be declared 'protected'.

Rule 5: The compiler shall not generate the methods that it would automatically generate for a non-abstract enumerated type, such as the "values()" or "valueOf(String)" methods, for an abstract enumerated type.


COMPILATION:

An abstract enumerated type with no declared supertype, such as:

abstract enum Foo {
   ...
}

shall be desugared to code resembling:

abstract class Foo<E extends Enum<E>> extends Enum<E> {
    ...
}

An abstract or non-abstract enumerated type with a declared supertype which is an abstract enumerated type, such as:

abstract enum Foo extends Bar {
   ...
}

shall be desugared to code resembling:

abstract class Foo<E extends Enum<E>> extends Bar<E> {
   ...
}

The Java compiler currently generates certain methods, such as values() and valueOf(String) for enumerated types. It shall not generate these for abstract enumerated types.

SIMPLE EXAMPLE:

These classes might be desugared to:

abstract class SuperEnum<E extends Enum<E>> extends Enum<E> {
    int getTheAnswerToTheUltimateQuestion() {
         return 42;
    }
}

class MyEnum extends SuperEnum<MyEnum> {
// The body of this non-abstract enumerated type,
// including generated methods, is exactly
// the same as the Java compiler would generate
// prior to this proposal. However, note that due to
// this proposal, it has a supertype of "SuperEnum<MyEnum>"
// instead of "Enum<MyEnum>".
}

ADVANCED EXAMPLE:

These classes might be desugared to:

abstract class Base1<E extends Enum<E>> extends Enum<E> {
    protected final int sharedData;
    public int getSharedData() {return sharedData;}
    protected Base1(int sharedData) {
        this.sharedData = sharedData;
    }
}

abstract class Base2<E extends Enum<E>> extends Base1<E> {
    protected final int otherSharedData;
    public int getOtherSharedData() {return sharedData;}
    protected Base2(int sharedData, int otherSharedData) {
        super(sharedData);
        this.otherSharedData = sharedData;
    }
}

class Instances extends Base2<Instances> {
// The body of this non-abstract enumerated type,
// including generated methods, is exactly
// the same as the Java compiler would generate
// prior to this proposal. However, note that due to
// this proposal, it has a supertype of "Base2<Instances>"
// instead of "Enum<Instances>".
}


TESTING:

Testing can be accomplished by declaring various abstract enumerated types, having them extend each other, and having non-abstract enumerated types extend them. Normal inheritance relations (method overriding, etc.) should exist among the resulting types.

LIBRARY SUPPORT:

No changes to supporting libraries are needed.

REFLECTIVE APIS:

The "Class.isEnum()" method might need to be adjusted to detect abstract enums as well as non-abstract ones. Note, however, that anonymous classes extending enumeration types are not considered enum types according to this method, so it is an open question as to whether abstract enumerated types should return 'true' or 'false' for this method. (I am leaning towards it returning 'false')

OTHER CHANGES:

Javadoc might possibly need to be updated to handle the possibility of an abstract enumerated type.

MIGRATION:

New abstract enumerated types can be created, as desired, to do code sharing between existing non-abstract enumerated types in an existing code base.

COMPATIBILITY

BREAKING CHANGES:

Since this syntax was previously considered a syntax error, no previously valid programs will be invalidated by it.

EXISTING PROGRAMS:

This feature should not change the semantics of existing class files. The features being added should desugar to normal method calls, class declarations, etc. Hence, I do not anticipate problems interacting with existing class files.

REFERENCES

EXISTING BUGS:

Add language support for abstract Enum's
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6507006

allow "abstract enum AbstractEnum" and "enum MyEnum extends AbstractEnum"
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6222244

Support for public abstract enum declaration
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570766

(The original enum proposal, the comments for which allude to the possibility of abstract enums)
Bug ID: 4401321 Add type-safe enums to Java
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4401321


URL FOR PROTOTYPE (optional):

None.





More information about the coin-dev mailing list