It seems I'm stuck with java generics again. Here is what I have:
Couple of classes:
class CoolIndex implements EntityIndex<CoolEntity>
class CoolEntity extends BaseEntity
Enum using classes above:
enum Entities {
COOL_ENTITY {
@Override
public <E extends BaseEntity, I extends EntityIndex<E>> Class<I> getIndexCls() {
return CoolIndex.class;
}
@Override
public <E extends BaseEntity> Class<E> getEntityCls() {
return CoolEntity.class;
}
}
public abstract <E extends BaseEntity, I extends EntityIndex<E>> Class<I> getIndexCls();
public abstract <E extends BaseEntity> Class<E> getEntityCls();
}
Function I need to call with use of result of getIndexCls()
function call:
static <E extends BaseEntity, I extends EntityIndex<E>> boolean isSomeIndexViewable(Class<I> cls)
The problem is that compiler complains about return CoolIndex.class;
and return CoolEntity.class;
and it's not clear to me why... Of course I can cast it to Class<I>
(first case) but it seems to me like I'm trying to mask my misunderstanding and it doesn't feel right.
To use Java generics effectively, you must consider the following restrictions: Cannot Instantiate Generic Types with Primitive Types. Cannot Create Instances of Type Parameters. Cannot Declare Static Fields Whose Types are Type Parameters.
So, anything that is used as generics has to be convertable to Object (in this example get(0) returns an Object ), and the primitive types aren't. So they can't be used in generics.
Which of these Exception handlers cannot be type parameterized? Explanation: we cannot Create, Catch, or Throw Objects of Parameterized Types as generic class cannot extend the Throwable class directly or indirectly.
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
The problem with getIndexCls
is that because it's generic, the type parameters can be interpreted to be any classes that fit the bounds on the declarations. You may think that CoolIndex.class
fits those bounds, and it does, but a caller of the method can supply their own type arguments which would be incompatible, e.g.:
Entities.COOL_ENTITY.<UncoolEntity, UncoolIndex>getIndexCls();
That would break type safety, so the compiler disallows this. You can cast to Class<I>
, but the compiler will warn you about an unchecked cast for the same reason. It will compile, but it can cause runtime problems as I've described.
Other situations can resolve such a situation by passing a Class<I>
object to make the type inference work properly, but that defeats the point of this method -- returning a Class<I>
object.
Other situations call for moving the generic type parameters from the method to the class, but you are using enums, which can't be generic.
The only way I've come up with to get something similar to compile is by removing the enum
entirely. Use an abstract class so you can declare class-level type parameters. Instantiate constants with the type arguments you desire.
abstract class Entities<E extends BaseEntity, I extends EntityIndex<E>> {
public static final Entities<CoolEntity, CoolIndex> COOL_ENTITY = new Entities<CoolEntity, CoolIndex>() {
@Override
public Class<CoolIndex> getIndexCls() {
return CoolIndex.class;
}
@Override
public Class<CoolEntity> getEntityCls() {
return CoolEntity.class;
}
};
// Don't instantiate outside this class!
private Entities() {}
public abstract Class<I> getIndexCls();
public abstract Class<E> getEntityCls();
}
This can be reproduced by much simpler example:
public <E extends BaseEntity> E get() {
return new BaseEntity(); // compilation error here
}
The problem in such declaration <E extends BaseEntity>
is that your method claims to return an instance of any type E
that caller should ask:
MyCoolEntity1 e = get(); // valid, E is MyCoolEntity1
MyCoolEntity2 e = get(); // valid, E is MyCoolEntity2
This code should be compile-time safe, so you have to cast result of your method to E
public <E extends BaseEntity> E get() {
return (E) new BaseEntity(); // no error, but unsafe warning
}
In your example it's pretty the same, you claim to return value of type Class<E>
:
public <E extends BaseEntity> Class<E> getEntityCls()
But return a concrete class SomeEntity.class
which is Class<CoolEntity>
You can add type cast return (Class<I>) CoolIndex.class;
/ return (Class<E>) CoolEntity.class;
You can replace enum with classes, since enums can not be generic and classes can
You can entirely remove generics, since there's no much value in it
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With