Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incompatible classes of java generics

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.

like image 483
mr.nothing Avatar asked Apr 26 '16 15:04

mr.nothing


People also ask

What is not allowed for generics Java?

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.

Why primitive data types are not allowed in Java generics?

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 the following Cannot be parameterized with a type using 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.

How do I restrict a generic type in Java?

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.


2 Answers

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();
}
like image 172
rgettman Avatar answered Sep 30 '22 23:09

rgettman


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>


OK, how should I fix that?
  1. You can add type cast return (Class<I>) CoolIndex.class; / return (Class<E>) CoolEntity.class;

  2. You can replace enum with classes, since enums can not be generic and classes can

  3. You can entirely remove generics, since there's no much value in it

like image 39
AdamSkywalker Avatar answered Oct 01 '22 00:10

AdamSkywalker