Given 2 interfaces:
public interface BaseInterface { }
public interface ExtendedInterface extends BaseInterface {}
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
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;
}
}
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();
}
}