scottb
scottb

Reputation: 10084

Java: How to achieve a dynamic cast from <? extends ...> to <E>

I have an enum that stores some metadata for a database table:

static enum DataTable implements TableMetaData {

    HISTORIES ("h", Hx.class, HxColumn.HPI_ID),
    :
    :

    private final String                              e_alias;
    private final Class<? extends Row>                e_tbl;
    private final ColumnMetaData                      e_idCol;

    DataTable(String                                  al,
          Class           <? extends Row>             c1, 
          ColumnMetaData                              id)
    {...}

    @Override public Class<? extends Row> modelClass() { return this.e_tbl; }
    :
    :
}

The field of interest is e_tbl which stores the model object class corresponding to the database table. All the model object classes in this project implement a Row interface. I'd like to be able to access this field in order to generate instances of this class.

My problem is that I'm using an unchecked cast that I don't know is safe and I haven't been able to figure out how to perform a runtime check to be sure that the model object matches. Here's the code in which the cast occurs:

static final <E extends Row> List<E> doQuery (
            final List<JdbcValue> pars,
            final String sql, 
            final TableMetaData dm
    ) 
{

    List<E> result = new ArrayList<E>();

    @SuppressWarnings("unchecked")
    Class<E> cls = (Class<E>) dm.modelClass();
    :
    :
}

I believe that this is a dangerous cast. The query is intended to generate E instances and those instances will be made using the model class that is contained in the supplied enum constant. The problem is I can't know that E matches the stored class token in the enum. This is a situation in which Class.asSubclass() does not seem to be able to help me.

The problem is that I have a field that is <? extends Row> and I need to make it compatible with <E extends Row>.

One solution would be to refactor the method to that it requires a Class<E> cls parameter rather than the enum. This would provide at least some compile-time assurance that the class token was appropriate for the type of the result list being generated. However this still does not enable me to use the data in the enum; I'd still have to cast it before the method call.

Is there a check I can use?

==================================================

UPDATED 12/6/14

It seems to me that this is a problem without a good, clean solution.

Enums do not support generic type parameters (they can't).

Having incompatible type information at compile-time will happen every time you try to store parameterized types in an enum constant.

Using a parameterized type-safe singleton pattern, as suggested, would enable the compiler to check type safety but the API I have is permeated by these enum classes and I don't feel that I can refactor them into regular classes.

To limit the damage, I wonder if the following reasoning is accurate:

These assumptions admit two approaches that I think have the same flaws. In the enum class, the accessor method is rewritten to:

@Override public <E extends Row> Class<E> modelclass() { 
    @SuppressWarning("unchecked")
    Class<E> cls = (Class<E>) this.e_tbl;
    return this.e_tbl; }
  1. I can store the data to the enum to a field that uses a raw type. The raw type interoperates with the generic accessor method and so it compiles but a warning is generated.

  2. I can store the data to the enum to a field that uses a <? extends Row> wildcard type. This won't compile with the generic accessor method (<E> extends Row>) so I have to use an unchecked cast. A warning is still generated.

Since I know that the enum data always extends Row, I think this is probably ok. What I can't do is guarantee that the enum data being retrieved is appropriate for the List type being generated. But I don't think there is any way to control for this aside from documenting that the API user must send the correct TableMetaData constant for the query being returned or the results will be "undefined."

Since enums won't work with generic types, I don't think there is a better solution.

Upvotes: 3

Views: 1005

Answers (1)

zapl
zapl

Reputation: 63955

One thing you could do:

  • Turn TableMetaData into TableMetaData<E> and define the class in terms of E
  • Get rid of the enum (because they don't support type parameters) and write one class per enum entry

So the interface looks roughly like

class Histories implements TableMetaData<SpecialRowType> {
     Class<SpecialRowType> modelClass();
}

Which should finally allow you to cast use it.

static final <E extends Row> List<E> doQuery (
            final List<JdbcValue> pars,
            final String sql, 
            final TableMetaData<E> dm
    ) 
{

    List<E> result = new ArrayList<E>();
    // type safe, because E refers to the same thing.
    Class<E> cls = dm.modelClass();
    :
}

Now the compiler can be sure that the ? extends Row class is the same as the E extends Row class, which do have a common ancestor but that doesn't mean that you can cast them to each other.

Upvotes: 2

Related Questions