2009-02-17 72 views
18

鉴于2个接口:检索类型参数

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

和具体类:

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

如何才能知道传递给BaseInterface接口类型参数?

(我可以通过调用类似

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

检索ExtendedInterface类型参数,但我看不出一个简单的方法来迭代到基础的通用接口,并得到任何有意义的背面)。

回答

20

这个问题一般不易完全解决。例如,如果它是一个内部类,则还必须考虑包含类的类型参数...

由于使用Java本身提供的对泛型类型的反射非常困难,我写了一个库辛勤工作:gentyref。见http://code.google.com/p/gentyref/ 对于你的榜样,使用gentyref,你可以这样做:

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

我不认为你可以,因为这些是真正的实例具体而不是类具体。考虑以下内容:

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

a是字符串的通用列表的事实是特定于实例a而不是类List的。因此,List.class对象的任何方法都不能告诉你泛型类型的类型是String类型的。虽然你的例子中的MyClass恰好为接口的genricized类型设置了值,但我不认为这将在接口Class对象实例中可用。

+0

类型参数可以从参数化类型的实例中检索。在你的例子中,可以挖掘a.getClass()来检索传递给ArrayList的String类型。 – Andy 2009-02-17 17:23:07

+0

err,不,不能, – 2009-02-18 00:03:16

+0

是的,对不起,我的评论是bobbins(深夜脑屁)。当然,我的意思是,如果你有一个ArrayList的非泛型封装类,那么你可以做到这一点。对?哎呦。 – Andy 2009-02-18 08:43:56

0

我认为我能想到的唯一选择是检查BaseInterface所声明的通用方法,而不是覆盖。

6

我不知道你想什么来实现的,什么是已知,什么不可以,但你可以递归的超接口是这样的:如果达到第二个层次

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

当然这样你只能得到你的名字T0和T1作为通用参数。如果你知道ExtendedInterfaceBaseInterface之间的关系,你不需要走太远的距离,因为你知道前者的哪个通用参数传递给后者。如果不是,你可能需要遍历它们的参数并找到匹配。在此基础上可能是一些:

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

我不认为有获得通用型底座接口的直接方式。

一种方法是在这样的接口声明的方法:

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

另外,我不知道你有过这些类什么样的控制。你总是可以断言,所有实施者具有的基本接口明确声明一样:从我回答我的问题

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

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

坏礼仪一次。

正如gix指出的那样,当你开始走上泛型类型的层次结构时,超出了第一个类型,你会失去有关类型参数的信息。

但重要的是:你得到的第一个通用接口的类型参数被实例化(在我的例子中,ExtendedInterface),并且你也得到了用于创建子接口的类型参数的名字。

因此,通过将TypeVariable名称映射为实际类型参数,可以确定基本接口的类型参数。

我稍后会用一些代码进行更新,但它确实有效(您可以从MyClass.class中确定用于实例BaseInterface的类型参数)。

更新 这是第一次通过绿灯点亮一些简单的单元测试。它需要工作......真正的问题是,问题是否值得这样一个可笑的解决方案?

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

}

1

还挺做你所追求的,但它仍然是不正确的。例如,它不处理Foo<T> implements Bar<Map<T>>的情况。你真正需要的是某种方式来问jvm“好吧,这里有一个类型列表,如果我将这些类型应用到这个泛型类型,我会返回什么样的实际类型?”

但是,这个代码有点不在你以后。

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

} 
2

使用Java Reflection API很难解决这个问题,因为需要解析所有遇到的类型变量。自12版以来的番石榴具有TypeToken类,其中包含完全解析的类型信息。

对于你的榜样,你可以这样做:

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

不过你需要记住的情况下,这只能在MyClass的不是一般的本身。否则,由于类型擦除,类型参数的值在运行时不可用。