Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Solve Java Generic Type Error with Abstract Class Method

I wants to achieve something like this in java, but getting compile time error:

The method onMessage(TextMessage, Class) in the type AbstractTestLoader is not applicable for the arguments (TextMessage, Class)

I understand the reason of that error, but I also feel there should be some way to achieve this with casting or may be some other way.

public abstract class AbstractTestLoader<T extends AbstractEntity<T>> {

    public void onMessage(TextMessage message) throws Exception {
        onMessage(message, this.getClass()); // I want to correct this line in such a way so that I can call below method with actual Class of {T extends AbstractEntity<T>}
    }

    public void onMessage(TextMessage message, Class<T> clazz) throws Exception {
        //here my original logic will go
    }
}
like image 363
Vishal Zanzrukia Avatar asked Mar 24 '17 04:03

Vishal Zanzrukia


3 Answers

It should be noticed that while Java generic got erased at runtime, there are limited reflection apis to retrieve them if they are present in the class file.

Here is a quick solution with these assumption:

  1. AbstractTestLoader is the direct super class.
  2. Sub classes does not use type wildcard when declaring super class, e.g. sub classes like this class GenericLoader<T extends SubclassAbstractEntity<T>> extends AbstractTestLoader<T> does not exist.

Here is the code:

public class AbstractTestLoader<T extends AbstractEntity<T>> {

    private static final ClassValue<Class<?>> TYPE_PARAMETER_CACHE = new ClassValue<Class<?>>() {
        @Override
        protected Class<?> computeValue(Class<?> type) {
            assert AbstractTestLoader.class.isAssignableFrom(type);
            assert type.getSuperclass() == AbstractTestLoader.class;
            Type genericSuperclass = type.getGenericSuperclass();
            assert genericSuperclass instanceof  ParameterizedType;
            Type entityType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
            assert entityType instanceof  Class<?>;
            return (Class<?>) entityType;
        }
    };

    @SuppressWarnings("unchecked")
    protected Class<T> getEntityType() {
        return (Class<T>) TYPE_PARAMETER_CACHE.get(this.getClass());
    }

    public void onMessage(Object message) throws Exception {
        onMessage(message, getEntityType()); // getting compile time error here
    }

    public void onMessage(Object message, Class<T> clazz) throws Exception {
        //here my original logic will go
    }
}

The getEntityType can be overriden in subclasses where those two assumptions fail.

like image 178
glee8e Avatar answered Nov 17 '22 15:11

glee8e


Finally, after couple of tries I just get simple and working solution, but still I am open to hear other best answers if possible. Thanks

public abstract class AbstractTestLoader<T extends AbstractEntity<T>> {

    abstract Class<T> getEntityType();

    public void onMessage(TextMessage message) throws Exception {
        onMessage(message, getEntityType());
    }

    public void onMessage(TextMessage message, Class<T> clazz) throws Exception {
        //here my original logic will go
    }
}
like image 40
Vishal Zanzrukia Avatar answered Nov 17 '22 15:11

Vishal Zanzrukia


Java implements generics via type erasure, which means that it's just a compile-time concept. When the program is running, there's no difference between an AbstractTestLoader<Foo> and a AbstractTestLoader<Bar>.

There are a few workarounds, like the one you discovered, where a class that's aware of the type of T at compile time can be made to provide an actual class type. But there's no way using pure generics to discover what the generic type is at runtime.

like image 2
StriplingWarrior Avatar answered Nov 17 '22 14:11

StriplingWarrior