Java 从泛型基接口的实例中检索类型参数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/557663/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:10:42  来源:igfitidea点击:

Retrieving type parameters from an instance of a generic base interface

javagenericsreflection

提问by Andy

Given 2 interfaces:

给定2个接口:

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?

如何找出传递给 BaseInterface 接口的类型参数?

(I can retrieve the ExtendedInterface type parameters by calling something like

(我可以通过调用类似的东西来检索 ExtendedInterface 类型参数

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

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

采纳答案by Wouter Coekaerts

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:

因为仅使用 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);
}

回答by Rob Di Marco

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.

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

回答by David Grant

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

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

回答by gix

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 ExtendedInterfaceand BaseInterfaceyou 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:

当然,如果您以这种方式达到第二级,则只会将您的名称 T0 和 T1 作为通用参数。如果您知道 和 之间的关系ExtendedInterfaceBaseInterface您实际上不必走那么远,因为您知道前者的哪个泛型参数传递给后者。如果没有,您可能必须遍历它们的参数并找到匹配项。基于此的东西可能:

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

回答by bruno conde

I don't think there's an directway 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]

回答by Andy

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.

正如 gix 所指出的,当您开始沿着泛型类型的层次结构向上走的那一刻,除了第一个之外,您会丢失有关类型参数的信息。

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.

但重要的一点是:您获得要实例化的第一个泛型接口的类型参数(在我的示例中为 ExtendedInterface),并且您还获得用于创建子接口的类型参数的名称。

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

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

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

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

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

}

}

回答by paulmurray

This kindadoes 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?"

有点像你所追求的那样,但它仍然是不对的。例如,它不处理Foo<T> implements Bar<Map<T>>. 您真正需要的是某种方式来询问 jvm“好吧,这是一个类型列表。如果我将这些应用于这个泛型类型,我会得到什么实际类型?”

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

}

回答by S?awek

This is difficult to solve using Java Reflection API because one needs to resolve all encountered type variables. Guava since version 12 has TypeTokenclass which contains fully resolved type info.

使用 Java 反射 API 很难解决这个问题,因为需要解析所有遇到的类型变量。从版本 12 开始,Guava 具有TypeToken类,其中包含完全解析的类型信息。

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.

您仍然需要记住,这仅适用于 MyClass 本身不是通用的情况。否则,由于类型擦除,类型参数的值在运行时不可用。