Retrieving type parameters from an instance of a generic base interface

24,716

Solution 1

This problem is not easy to fully solve in general. For example, you also have to take type parameters of the containing class into account if it's an inner class,...

Because reflection over generic types is so hard using just what Java itself provides, I wrote a library that does the hard work: gentyref. See http://code.google.com/p/gentyref/ For your example, using gentyref, you can do:

Type myType = MyClass.class;

// get the parameterized type, recursively resolving type parameters
Type baseType = GenericTypeReflector.getExactSuperType(myType, BaseInterface.class);

if (baseType instanceof Class<?>) {
    // raw class, type parameters not known
    // ...
} else {
    ParameterizedType pBaseType = (ParameterizedType)baseType;
    assert pBaseType.getRawType() == BaseInterface.class; // always true
    Type typeParameterForBaseInterface = pBaseType.getActualTypeArguments()[0];
    System.out.println(typeParameterForBaseInterface);
}

Solution 2

I don't know what exactly you are trying to achieve, and what is known and what not, but you can recurse to the superinterface like this:

Type[] interfaces = MyClass.class.getGenericInterfaces();

ParameterizedType extInterfaceType = (ParameterizedType)interfaces[0];
Class<?> extInterfaceClass = (Class<?>)extInterfaceType.getRawType();

Type[] baseInterfaces = extInterfaceClass.getGenericInterfaces();
ParameterizedType baseInterfaceType = (ParameterizedType)baseInterfaces[0];
Class<?> baseInterfaceClass = (Class<?>)baseInterfaceType.getRawType();

Of course if you reach the second level that way you get only your names T0 and T1 as generic parameters. If you know the relationship between ExtendedInterface and BaseInterface you don't really have to go that far since you know which generic parameter of the former is passed to the latter. If not, you probably would have to loop through their parameters and find a match. Something based on this probably:

Type[] params = extInterfaceClass.getTypeParameters();
for (Type param : params) {
    if (param == baseInterfaceType.getActualTypeArguments()[0]) {
        // ...
    }
}

Solution 3

This is difficult to solve using Java Reflection API because one needs to resolve all encountered type variables. Guava since version 12 has TypeToken class which contains fully resolved type info.

For your example, you can do:

TypeToken<? extends T> token = TypeToken.of(MyClass.class);
ParameterizedType type =
    (ParameterizedType) token.getSupertype(BaseInterface.class).getType();
Type[] parameters = type.getActualTypeArguments();

Still you need to remember that this only works for cases when MyClass is not generic itself. Otherwise the value of type parameters is not available at runtime due to type erasure.

Solution 4

I don't think there's an direct way of getting the generic type of the base interface.

One way would be to declare a method in the interface like this:

public interface BaseInterface<T> {
    Class<T> getGenericClass();
}

Also, I don't know what kind of control you have over these classes. You can always assert that all implementers have the base interface explicitly declared like:

public class MyClass implements ExtendedInterface<String, Object>, BaseInterface<String>{ }

and

MyClass.class.getGenericInterfaces()[1].getActualTypeArguments()[0]

Solution 5

This kinda does what you are after, but it's still not right. For instance, it doesn't handle the case where Foo<T> implements Bar<Map<T>> . What you really need is some way to ask the jvm "ok, here is a list of types. What actual type do I get back if I apply these to this generic type?"

But, this code kinda does what you are after.

import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.util.*;

interface BaseInterface<T> {}
interface FirstArg<T1,T2> extends BaseInterface<T1>{}
interface SecondArg<T1,T2> extends BaseInterface<T2>{}

class First implements FirstArg<Number, String> {}
class Second implements SecondArg<Number, String> {}


public class Example {
    public static void main(String[] av) {
        new Example().go();
    }

    void go() {
        test(First.class);
        test(Second.class);
    }

    void test(Class<?> c1) {        
        ParameterizedType t2 = (ParameterizedType) c1.getGenericInterfaces()[0];
        System.out.println(c1 + " implements " + t2 );

        Class<?> c2 = (Class<?>)t2.getRawType();
        GenericDeclaration g2 = (GenericDeclaration) c2;

        System.out.println(t2 + "  params are " + Arrays.asList(g2.getTypeParameters()));

        System.out.println("So that means");
        for(int i = 0; i<t2.getActualTypeArguments().length; i++) {
            System.out.println("Parameter " + c2.getTypeParameters()[i] + " is " + t2.getActualTypeArguments()[i]);
        }

        ParameterizedType t3 =  (ParameterizedType) c2.getGenericInterfaces()[0];
        System.out.println(t2 + "  implements " + t3);

        System.out.println("and so that means we are talking about\n" + t3.getRawType().toString() + " <");
        for(int i = 0 ; i< t3.getActualTypeArguments().length; i++) {
            System.out.println("\t" + t3.getActualTypeArguments()[i] + " -> " 
            + Arrays.asList(g2.getTypeParameters()).indexOf(t3.getActualTypeArguments()[i])
            + " -> " + 
            t2.getActualTypeArguments()[Arrays.asList(g2.getTypeParameters()).indexOf(t3.getActualTypeArguments()[i])]
            );
        }

        System.out.println(">");
        System.out.println();
    }

}
Share:
24,716
Andy
Author by

Andy

Updated on July 09, 2022

Comments

  • Andy
    Andy almost 2 years

    Given 2 interfaces:

    public interface BaseInterface<T> { }
    public interface ExtendedInterface<T0, T1> extends BaseInterface<T0> {}
    

    and a concrete class:

    public class MyClass implements ExtendedInterface<String, Object> { }
    

    How do I find out the type parameter passed to the BaseInterface interface?

    (I can retrieve the ExtendedInterface type parameters by calling something like

    MyClass.class.getGenericInterfaces()[0].getActualTypeArguments()
    

    but I can't spot an easy way to recurse into any base generic interfaces and get anything meaningful back).

  • Andy
    Andy about 15 years
    Type parameters can be retrieved from an instance of parameterized type. In your example, a.getClass() can be mined to retrieve the type String that was passed to ArrayList.
  • Andy
    Andy about 15 years
    yes, sorry, my comment was bobbins (late night brain fart). What I meant, of course, was that if you have a non-generic wrapper class around the ArrayList, then you can do it. Right? Whoops.
  • Andy
    Andy about 15 years
    This is something like how I implemented it also. I used type variable names, though, as argument order might not be guaranteed (I think) (in my example, ExtendedInterface could pass T1 to the base interface).
  • paulmurray
    paulmurray about 15 years
    Well, that's exactly what my code handles - you'll note two interfaces, one which passes param 1, and one which passes param 2.
  • pAkY88
    pAkY88 about 15 years
    This indeed looks like a good start, similar to what gentyref does. It still requires some work to be as complete. Think about nested classes, nested generics, and then you'll notice you also need to handle wildcards in cases like interface I<T> implements BaseInterface<List<? extends T>>,...
  • Andy
    Andy about 15 years
    Very good points, thank you Wouter. I will update this at a later date with a more complete solution. For the moment, it suits my purposes in a project with a tight deadline, so I'll leave it for now.
  • nessa.gp
    nessa.gp over 3 years