views:

2219

answers:

7

Hello,

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).

A: 

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
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
err, no it can't
oxbow_lakes
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
A: 

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
+4  A: 

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
+1  A: 

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
+1  A: 

Hi, 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 loose 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;
}

}

Andy
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>>,...
Wouter Coekaerts
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
+1  A: 

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
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
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
+5  A: 

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