1

I have an interface with generic

public interface MessageCallback<T extends JsonModel> {
     void onMessage(T t);
}

And I wanna get instance of T. I found a solution where I can use reflection

public static <T> Class<T> getGenericClass(Class actualClass, int index) {
    ParameterizedType genericSuperclass = (ParameterizedType) actualClass.getGenericSuperclass();

    // Get generic class
    return (Class<T>) genericSuperclass.getActualTypeArguments()[index];
}

But it throws exception.

java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType

I wanna know is there a solution without using reflection?

Extra. I also found this

class Foo<T> {
final Class<T> typeParameterClass;

public Foo(Class<T> typeParameterClass) {
    this.typeParameterClass = typeParameterClass;
}

public void bar() {
    // you can access the typeParameterClass here and do whatever you like
}
}

But its only for class. How I can solve my problem? Thanks for your answers.

And if you wanna know it isn't clean Java. It's in Android

Ker_ Jen
  • 163
  • 8
  • *"I wanna get instance of T"* `t` is an instance of `T`, so not sure what you're really after. The question looks like an [XY problem](https://meta.stackexchange.com/q/66377/351454). What are you really trying to accomplish? – Andreas Jun 23 '18 at 19:31
  • @Andreas I meant I wanna get the class that I have put in T – Ker_ Jen Jun 23 '18 at 19:34
  • What exception do you get? The reflective solution doesn't work in all cases, but it works for some. You also need to use it correctly. Wherever you got the code from, you need to read all of the comments and stuff carefully. – Radiodef Jun 23 '18 at 19:36
  • @Radiodef I have added the exception in the question – Ker_ Jen Jun 23 '18 at 19:37
  • [There's an explanation of the reflective code here.](https://stackoverflow.com/a/13974262/2891664) The problem with it is that it only works if the type argument is a class and is supplied to an actual superclass. For example, if you did `class FloatList extends ArrayList {}` then it's possible to retrieve `Float.class` at runtime using that code. It won't work if you are creating your generic objects like `new ArrayList()` or `class C { List list = new ArrayList() {}; }`. – Radiodef Jun 23 '18 at 19:56

2 Answers2

1

About your first code, Class.getGenericSuperclass() returns the type representing the direct superclass of this class, that it Object in your case.

Class.getGenericInterfaces() that returns the Types representing the interfaces directly implemented by this class would be a better choice to retrieve the generic defined in the MessageCallback interface.
But it will probably not help you as the generic type will be which one statically declared in the MessageCallback interface : JsonModel and not which one defined in the subclass declaration that extends the interface.

About your second code, you should not worry about whether the parameter to capture the generic is Class or Type. For example, the types declared in a generic class such as interface MessageCallback<T extends JsonModel> or a subclass of a generic class such as MessageCallbackImpl extends MessageCallback<MyJsonModel> are necessary classes as parameterized types can only be Classes. So storing the type of the generic type as Class is perfectly valid.
The Type class that is the common superinterface for all types in Java is much broader (raw types, parameterized types, array types, type variables and primitive types)

So to come back to your question : what you need is having a way to refer at runtime the generic type defined at compile and this idiom is a classical way to do that :

public Foo(Class<T> typeParameterClass) {
    this.typeParameterClass = typeParameterClass;
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
0

You were on the right track. Now suppose we have a generic interface, as in your example:

public interface MessageCallback<T extends JsonModel> {
     void onMessage(T t);
}

And a concrete implementation of it:

class FooMessage {}

public class MyMessageCallback 
       implements MessageCallback<FooMessage extends JsonModel> {
    @Override
    public void onMessage(FooMessage t) {
        System.out.println("Received foo");
    }
}

You want to find the type passed into generic interface, MessageCallback, in this case FooMessage.

We can use reflection to find all interfaces that MyMessageCallback implements and for each interface, check if it is a generic interface then return all of its type parameters.

public static <T> Class<?>[] getGenericInterfaceParameter(Class clazz, Class<T> interfaceType) {
    Class<?>[] result = new Class<?>[0];
    // make sure interfaceType is a generic interface.
    if(!interfaceType.isInterface() || interfaceType.getTypeParameters().length < 1) {
        return result;
    }
    // get all interfaces implemented by concrete class.
    Type[] interfaceTypes = clazz.getGenericInterfaces();

    // for each interface the concrete class implements
    // we check if that interface is actually equal to interfaceType
    // and is a parametrized type,
    // i.e has a type parameter.
    // Once a match is found, we return the type parameters.
    for(Type it : interfaceTypes) {
        if(it instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) it;
            if(!parameterizedType.getRawType().equals(interfaceType)) {
                continue;
            }
            Type[] typeParameters = parameterizedType.getActualTypeArguments();
            result = new Class[typeParameters.length];
            for(int j = 0; j < typeParameters.length; j++) {
                result[j] = (Class) typeParameters[j];
            }
        }
    }

    return result;
}

Use the method to find type parameter of MyMessageCallback:

Class<?>[] params = getGenericInterfaceParameter(
                       MyCallback.class,
                       MessageCallback.class
                    );
System.out.println(params[0].getTypeName()); // MyMessageCallback
Zaiyang Li
  • 102
  • 7