Reputation: 10084
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:
Data associated with enum constants is static and final; here it is also all immutable.
I fully control the data that goes into the enum constants, so I know at run-time that the e_tbl
field will always be a Class
object of some type that extends Row
.
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; }
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.
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
Reputation: 63955
One thing you could do:
TableMetaData
into TableMetaData<E>
and define the class in terms of E
enum
(because they don't support type parameters) and write one class
per enum entrySo 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