从通用基接口的实例中检索类型参数

时间:2009-02-17 16:37:48

标签: java generics reflection

给出2个接口:

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

和具体的课程:

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

如何找到传递给BaseInterface接口的type参数?

(我可以通过调用类似

的东西来检索ExtendedInterface类型参数
MyClass.class.getGenericInterfaces()[0].getActualTypeArguments()

但是我找不到一种简单的方法来递归到任何基础通用接口并获得任何有意义的东西。)

9 个答案:

答案 0 :(得分: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);
}

答案 1 :(得分: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]) {
        // ...
    }
}

答案 2 :(得分:4)

使用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本身不是通用的情况。否则,由于类型擦除,类型参数的值在运行时不可用。

答案 3 :(得分:1)

我认为没有直接方式获取基本接口的泛型类型。

一种方法是在界面中声明一个方法,如下所示:

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

另外,我不知道你对这些课程有什么样的控制。您始终可以断言所有实现者都明确声明了基本接口,如:

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

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

答案 4 :(得分: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();
    }

}

答案 5 :(得分:0)

我不认为你可以,因为这些实际上是特定于实例而不是特定于类。请考虑以下事项:

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

a是通用的字符串列表这一事实特定于实例a而不是类List。因此,List.class对象的所有方法都不能告诉您泛型类型的类型为String。虽然您的示例中的MyClass碰巧为接口的genricized类型设置了值,但我认为这不会在接口Class对象实例中可用。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

我回答自己的问题后再次表达了礼貌。

正如gix指出的那样,当你开始走向泛型类型的层次结构时,除了第一个之外,你就会丢失有关类型参数的信息。

但重要的一点是:你得到第一个通用接口的类型参数(在我的例子中,ExtendedInterface),你还得到用于创建子接口的类型参数的名称。

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

稍后我将使用一些代码更新,但它确实有效(您可以确定用于实例BaseInterface的类型参数,来自MyClass.class)。

<强>更新 这是第一次绿灯点亮一些简单的单元测试。它需要工作......真正的问题是,问题是否值得这么荒谬的解决方案?

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

}

答案 8 :(得分:0)