20

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
  • 3,596
  • 8
  • 34
  • 33

9 Answers9

20

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);
}
Wouter Coekaerts
  • 9,395
  • 3
  • 31
  • 35
6

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]) {
        // ...
    }
}
gix
  • 5,737
  • 5
  • 34
  • 40
4

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.

Sławek
  • 1,450
  • 14
  • 14
1

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]
bruno conde
  • 47,767
  • 15
  • 98
  • 117
1

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();
    }

}
paulmurray
  • 3,355
  • 1
  • 22
  • 17
  • 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). – Andy Feb 18 '09 at 14:10
  • Well, that's exactly what my code handles - you'll note two interfaces, one which passes param 1, and one which passes param 2. – paulmurray Feb 19 '09 at 08:56
0

Apache Commons has a utility for doing just this... https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/reflect/TypeUtils.html

See how it is used on their github page: https://github.com/apache/commons-lang/blob/72be39f4facb4a5758b9f646309328b764216da3/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java

Ryan Walls
  • 6,962
  • 1
  • 39
  • 42
  • 1
    New link: https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/reflect/TypeUtils.html – nessa.gp Aug 11 '20 at 12:41
0

I don't think you can as these are really instance specific not class specific. Consider the following:

List<String>  a = new ArrayList<String>();

The fact that a is generic list of Strings is specific to the instance a and not to the class List. Thus, none of the methods of the List.class object could tell you that the genericized type would be of type String for a. Although MyClass in your example happens to have set values for the genricized types of the interface, i do not think this would be available at the interface Class object instance.

Rob Di Marco
  • 43,054
  • 9
  • 66
  • 56
  • 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 Feb 17 '09 at 17:23
  • 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 Feb 18 '09 at 08:43
0

I think about the only option I can think of is to inspect a generic method which is declared by BaseInterface, and not overridden.

David Grant
  • 13,929
  • 3
  • 57
  • 63
0

bad etiquette again from me answering my own question.

As gix pointed out, the moment you start walking up a hierarchy of generic types, beyond the first, you lose information about type arguments.

But the important bits are: You get the type arguments of the first generic interface to be instanced (in my example, ExtendedInterface), and you also get the names of the type parameters used to create the sub-interfaces.

So, it is possible to determine the type arguments to base interfaces by keeping a map of TypeVariable names to actual type arguments.

I will update with some code later, but it does work (you can determine the type parameter used to instance BaseInterface, from MyClass.class).

Update This is a first pass that green lights some simple unit tests. It needs work... The real question is, does the problem merit such a ludicrous solution?

public class GenericReflectionUtils
{
@SuppressWarnings("unchecked")
public static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, Class concreteClass)
{
    if (!baseInterface.isAssignableFrom(concreteClass))
    {
        throw new IllegalArgumentException("Illegal base interface argument");
    }
    if (concreteClass.getTypeParameters().length > 0)
    {
        throw new IllegalArgumentException("Can't determine the type arguments of a generic interface of a generic class");
    }
    for (Type genericInterface : concreteClass.getGenericInterfaces())
    {
        List<Class> result = null;
        if (genericInterface instanceof Class)
        {
            result = getGenericInterfaceTypeArguments(baseInterface,(Class)genericInterface);
        }
        else
        {
            result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterface);
        }
        if (result != null)
        {
            return result;
        }
    }
    return null;
}


public static Class getClass(Type type)
{
    if (type instanceof Class)
    {
        return (Class) type;
    }
    if (type instanceof ParameterizedType)
    {
        return getClass(((ParameterizedType) type).getRawType());
    }
    if (type instanceof GenericArrayType)
    {
        Type componentType = ((GenericArrayType) type).getGenericComponentType();
        Class<?> componentClass = getClass(componentType);
        if (componentClass != null)
        {
            return Array.newInstance(componentClass, 0).getClass();
        }
        return null;
    }
    return null;
}

@SuppressWarnings("unchecked")
private static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, ParameterizedType currentType)
{
    Class currentClass = getClass(currentType);
    if (!baseInterface.isAssignableFrom(currentClass))
    {
        //  Early out - current type is not an interface that extends baseInterface
        return null;
    }

    Type[] actualTypeArguments = currentType.getActualTypeArguments();

    if (currentClass == baseInterface)
    {
        //  currentType is a type instance of the base generic interface. Read out the type arguments and return 
        ArrayList<Class> typeArgs = new ArrayList<Class>(actualTypeArguments.length);
        for (Type typeArg : actualTypeArguments)
        {
            typeArgs.add(getClass(typeArg));
        }

        return typeArgs;
    }

    //  currentType is derived
    Map<String, Class> typeVarMap = createTypeParameterMap(currentType, null);

    for (Type genericInterfaceType : currentClass.getGenericInterfaces())
    {
        List<Class> result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterfaceType, typeVarMap);
        if (result != null)
        {
            return result;
        }
    }
    return null;
}

private static Map<String, Class> createTypeParameterMap(ParameterizedType type, Map<String, Class> extendedTypeMap)
{
    Map<String, Class> typeVarMap = new HashMap<String, Class>();
    Type[] typeArgs = type.getActualTypeArguments();
    TypeVariable[] typeVars = getClass(type).getTypeParameters();
    for (int typeArgIndex = 0; typeArgIndex < typeArgs.length; ++typeArgIndex)
    {
        //  Does not deal with nested generic arguments...
        Type typeArg = typeArgs[typeArgIndex];
        if (typeArg instanceof TypeVariable)
        {
            assert extendedTypeMap != null;
            TypeVariable typeVar = (TypeVariable)typeArg;
            typeVarMap.put(typeVars[typeArgIndex].getName(), extendedTypeMap.get(typeVar.getName()));
            continue;
        }
        typeVarMap.put(typeVars[typeArgIndex].getName(), getClass(typeArgs[typeArgIndex]));
    }

    return typeVarMap;
}

private static List<Class> createTypeParameterList(Map<String, Class> typeParameterMap, ParameterizedType type)
{
    ArrayList<Class> typeParameters = new ArrayList<Class>(typeParameterMap.size());
    for (Type actualType : type.getActualTypeArguments())
    {
        if (actualType instanceof TypeVariable)
        {
            //  Handles the case when an interface is created with a specific type, rather than a parameter
            typeParameters.add(typeParameterMap.get(((TypeVariable)actualType).getName()));
            continue;
        }
        typeParameters.add(getClass(actualType));
    }
    return typeParameters;
}

@SuppressWarnings("unchecked")
private static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, ParameterizedType currentType, Map<String, Class> currentTypeParameters)
{
    Class currentClass = getClass(currentType);
    if (!baseInterface.isAssignableFrom(currentClass))
    {
        //  Early out - current type is not an interface that extends baseInterface
        return null;
    }

    if (currentClass == baseInterface)
    {
        return createTypeParameterList(currentTypeParameters, currentType);
    }

    currentTypeParameters = createTypeParameterMap(currentType, currentTypeParameters);
    for (Type genericInterface : currentClass.getGenericInterfaces())
    {
        List<Class> result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterface, currentTypeParameters);
        if (result != null)
        {
            return result;
        }
    }

    return null;
}

}

Dzinx
  • 55,586
  • 10
  • 60
  • 78
Andy
  • 3,596
  • 8
  • 34
  • 33
  • 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 implements BaseInterface>,... – Wouter Coekaerts Feb 21 '09 at 15:31
  • 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. – Andy Feb 22 '09 at 00:14