Solve Java Generic Type Error with Abstract Class Method
Asked Answered
A

3

12

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
    }
}
Aboveboard answered 24/3, 2017 at 4:13 Comment(5)
Why do you feel so? Maybe you want to add your understanding of the underlying problem, to ensure that everybody is on the same page. As we have to agree on the root cause when exploring the solution space.Alerion
Hi @GhostCat, I have already tried to explore my underlying problem with comment line 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>} Also, you can refer solution posted by me as well for better solution if you feel to answer.Aboveboard
Will AbstractTestLoader be the direct super class?Tartuffery
@GhostCat: Vishal's intuition is good on this one. There are other languages like C# that implement reified generics so you could say something like T.class, and there's been some talk about changing Java to do the same in some future versionNewborn
I know most of that. I wanted to be sure that the OP knows about them. In addition to that, searching duplicate questions to close out is a nuisance on the mobile phone. That is why commented first to ensure the OP really understands the problem.Alerion
A
4

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
    }
}
Aboveboard answered 24/3, 2017 at 4:33 Comment(0)
T
5

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.

Tartuffery answered 24/3, 2017 at 4:43 Comment(0)
A
4

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
    }
}
Aboveboard answered 24/3, 2017 at 4:33 Comment(0)
N
2

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.

Newborn answered 24/3, 2017 at 4:37 Comment(4)
Actually, there is a way. Please see @glee8e's answerRetainer
@FedericoPeraltaSchaffner thanks for mentioning me, but that @ mark doesn't trigger a system notification to me because that trailing 's makes the system think you are calling someone named glee8e's. just a kind reminder :)Tartuffery
@FedericoPeraltaSchaffner: glee8e's answer is a good one, and will probably work well in many cases. I don't feel that it invalidates my answer, though: in my mind it qualifies as one of the workarounds I mentioned "where a class that's aware of the type of T at compile time can be made to provide an actual class type." In Vishal's answer it does this via an abstract method; in glee8e's case it does so by requiring the consuming classes to be declared in a specific way.Newborn
@Newborn Fair enough. It's true that at runtime there's no way to discover the generic type of a class, however (although strange) it is possible to discover the generic type of the superclass (if it's generic).Retainer

© 2022 - 2024 — McMap. All rights reserved.