3

In Effective Java, 2nd Ed., Joshua Bloch develops the "extensible enum pattern" (item 34). Since enums cannot be subclassed, he proposes unifying groups of related enums by having each enum implement a common type, i.e. interface. This allows enums to be referred to by their unifying type name. The obvious problem with this solution is that type safety is somewhat compromised because it is at least theoretically possible to substitute any non-enum object for an enum by simply creating a class that implements the unifying interface.

To address this problem, a solution is proposed. Here is the method declaration used from the book that takes an enum. The sample application has two enums (BasicOperation and ExtendedOperation), both of which implement a unifying type interface called Operation. The method is designed to accepted any enum of the proper type:

private static <T extends Enum<T> & Operation> void test(
        Class<T> opset, double x, double y) {
    :
    :
}

The reason this works is because the generic method type parameter assures that the class literal supplied as the first argument to the function is both an enum type and an Operation type.

Here is some code from an enum that I am using. This enum is one of a group of enums that I use to describe the metadata for a database column from any of several database tables I am using in my application. Each table has it's own enum that describes these data and they are all unified by implementing the ColumnMetaData<T> interface (where T corresponds to the class for the database table).

class Client extends DB {  // Class for the Clients table

    // MetaData for all the columns in Client
    static enum Column implements ColumnMetaData<Client> {

            CLIENT_ID   (...
                :
                :
            );
    }
}

I would like to use a value class in my application called Datum. It is intended to keep together a database column's value with its column enum.

Here is my problem:

I cannot use a generic method parameter in the constructor for Datum. How can I tell the compiler that one of the fields in Datum must implement both ColumnMetaData<table> and Enum<table.Column>? Currently, I am using the following:

static class Datum {

    private final Object                        val;  
    private final ColumnMetaData<? extends DB > col;

    private Datum(Object val, ColumnMetaData<? extends DB> col) {
        this.val = val;
        this.col = col;
    }

    // assorted static factories here...
    :
    :
}    

This works, but the value is not recognized as an enum type and I want to use the associated enum constants with EnumSet and EnumMap.

Is there an elegant solution I am not seeing?

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
scottb
  • 9,908
  • 3
  • 40
  • 56
  • 1
    try generify `Datum` class – ZhongYu Sep 09 '13 at 19:05
  • This question is a probable duplicate of http://stackoverflow.com/questions/10037008/how-to-declare-a-class-object-such-that-is-is-an-enum-and-an-interface-in-jav?rq=1 – scottb Sep 09 '13 at 19:06
  • So to be clear, you can declare static factory methods like ` & ColumnMetaData extends DB>> Datum create(Object val, T col)` but cannot make `Datum` itself generic? – Paul Bellora Sep 09 '13 at 20:04
  • Also: "How can I tell the compiler that one of the fields in Datum must implement both `ColumnMetaData` and `Enum`" - I feel like this must be a typo. If the field must be an `Enum` then it must be exactly a `table.Column` and there's no need for generics at all.
    – Paul Bellora Sep 09 '13 at 20:15
  • @PaulBellora: I see my problem as an extension of the issue addressed in the book. I want to pass a parameter to the constructor in a way so that the compiler understands that the parameter is an enum that implements ColumnMetaData. I don't want to generify Datum because there are contexts for Datum in which there is no associated column, i.e. Datum will not strictly depend upon a type parameter. I haven't shown all the code, but Datum has two overloaded contructors ... one which takes a ColumnMetaData and one that doesn't.
    – scottb Sep 09 '13 at 21:47
  • @PaulBellora: Yeah, it did occur to me just as I posted that I could use Generic Method Parameter declarations for all the static factories. I think this would work ... except that I'm still puzzled how to store it in the object. The local variable is declared `ColumnMetaData extends DB>`. since this variable will always hold an enum constant, how can I inform the compiler of this fact? – scottb Sep 09 '13 at 21:50
  • I think I know one way at least (it isn't pretty), but first I'd like to see examples of how exactly you want to use the enum variable. You mention `EnumSet`, `EnumMap` etc. - how would you use these collections with one enum constant whose specific type you don't know (seemingly ruling out mixing it with other such enum constants)? – Paul Bellora Sep 10 '13 at 01:08

1 Answers1

1

Here's one way - I've used it and it works nicely.

Use a base class with a generic parameter:

public class Table<Column extends Enum<Column> & Table.Columns> {
  // Name of the table.
  protected final String tableName;
  // All of the columns in the table. This is actually an EnumSet so very efficient.
  protected final Set<Column> columns;

  /**
   * The base interface for all Column enums.
   */
  public interface Columns {
    // What type does it have in the database?
    public Type getType();
  }

  // Small list of database types.
  public enum Type {
    String, Number, Date;
  }

  public Table(String tableName,
               Set<Column> columns) {
    this.tableName = tableName;
    this.columns = columns;
  }
}

Then subclass that and provide a concrete enum implementing the interface for each table you want.

public class VersionTable extends Table<VersionTable.Column> {
  public enum Column implements Table.Columns {
    Version(Table.Type.String),
    ReleaseDate(Table.Type.Date);
    final Table.Type type;

    Column(Table.Type type) {
      this.type = type;
    }

    @Override
    public Type getType() {
      return type;
    }
  }

  public VersionTable() {
    super("Versions", EnumSet.allOf(Column.class));
  }
}

I've use seriously minimal code here with loads of detail trimmed out but I hope you can see how it all fits.

Note that I have used an EnumSet instead of your Class because there's more detail in an EnumSet.

Note also that maximum type safety is preserved.

It is interesting how many useful features fall out of this pattern. For example you can define sets of columns using EnumSet so you instantly have union and intersection tricks as a freebie.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213