在运行时获取泛型类型:在某些情况下有效,但在其他情况下无效 - 为什么?

Get generic type at runtime: works in some cases, but not in others - why?

我正在尝试获取 class 或接口实现的通用类型。我知道这有一些风险和怪癖,但我试图了解什么是可能的。

这是我的示例代码:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.LinkedList;

public class Main {

    public interface MyInterface<T> {
    }

    public static class BaseImpl<T> implements MyInterface<T> {
    }

    public static class TypedImpl extends BaseImpl<Integer> {
    }

    public static void main(String[] args) throws Exception {
        LinkedList<MyInterface<Integer>> instances = new LinkedList<>();

        // 1. anonymous class from interface
        instances.add(new MyInterface<Integer>() {
        });

        // 2. class extending interface
        instances.add(new BaseImpl<Integer>());

        // 3. class with pre-defined generic type
        instances.add(new TypedImpl());

        for (MyInterface<Integer> instance : instances) {
            System.out.println("----------------------------------------------");

            Class clazz = instance.getClass();
            Type genericSuper = clazz.getGenericSuperclass();
            Type[] genericInterfaces = clazz.getGenericInterfaces();
            Class target = null;

            System.out.println("class: " + clazz.getName());
            System.out.println("generic super: " + genericSuper);
            System.out.println("generic interfaces: " + Arrays.asList(genericInterfaces));

            // attempt to 'extract' generic type
            if (genericSuper instanceof ParameterizedType) {
                target = getGeneric((ParameterizedType) genericSuper);
            } else if (genericInterfaces.length > 0) {
                for (Type genericInterface : genericInterfaces) {
                    if (genericInterface instanceof ParameterizedType) {
                        target = getGeneric((ParameterizedType) genericInterface);

                        if (null != target) {
                            break;
                        }
                    }
                }
            }

            System.out.println("TARGET: " + target);
        }
    }

    // attempt to get type argument
    public static Class getGeneric(ParameterizedType type) {
        if (MyInterface.class.isAssignableFrom((Class) type.getRawType())) {
            Type typeArg = type.getActualTypeArguments()[0];

            try {
                return (Class) typeArg;
            } catch (ClassCastException e) {
                System.out.println("cast exception for '" + typeArg + "'");
            }
        }

        return null;
    }

}

这个输出是:

----------------------------------------------
class: Main
generic super: class java.lang.Object
generic interfaces: [Main.Main$MyInterface<java.lang.Integer>]
TARGET: class java.lang.Integer
----------------------------------------------
class: Main$BaseImpl
generic super: class java.lang.Object
generic interfaces: [Main.Main$MyInterface<T>]
cast exception for 'T'
TARGET: null
----------------------------------------------
class: Main$TypedImpl
generic super: Main.Main$BaseImpl<java.lang.Integer>
generic interfaces: []
TARGET: class java.lang.Integer

所以,我的目标是让 target 变量的值为 Integer.class。对于匿名 class (#1) 和显式类型实现 (#3),我能够按预期找到目标。为什么它在这些实例中起作用而不是在#2(BaseImpl 实例)的情况下?还有另一种方法可以做到这一点吗? (我知道通过实现 class 的构造函数传递目标 Class 的常见解决方法,但是我有兴趣动态地执行此操作)

我参考了以下资源:

非常感谢,

亚伯

在情况 1 和 3 中,类型是 classes MainMain$TypedImpl 上的反射数据的一部分,因此您可以访问它。

在情况 2 中,您只有实例数据,由于类型擦除,该数据不包含任何类型信息。因此,您无法在此处访问通用类型。

进一步说明:

在运行时,系统无法区分 BaseImpl<Integer> i;BaseImpl<String> s;,即由于类型擦除,变量 is 都是类型 BaseImpl

相比之下,TypedImpl t; 已知属于 TypedImpl 类型,使用反射可以提取 extendsimplements 语句中提供的通用类型。

旁注,因为您正在尝试了解什么是可能的:

如果您将 BaseImpl 定义为 BaseImpl<T extends Number> implements MyInterface<T>,您可以从 class BaseImpl 上的反射数据中提取 T 的上限,即您将能够知道 T 必须是 Number 或子类型(或接口情况下的实现)。

来自 http://tutorials.jenkov.com/java-reflection/generics.html(粗体是我的)

Using Java Generics typically falls into one of two different situations:

Declaring a class/interface as being parameterizable. Using a parameterizable class. When you write a class or interface you can specify that it should be paramerizable. This is the case with the java.util.List interface. Rather than create a list of Object you can parameterize java.util.List to create a list of say String.

When runtime inspecting a parameterizable type itself, like java.util.List, there is no way of knowing what type is has been parameterized to. This makes sense since the type can be parameterized to all kinds of types in the same application. But, when you inspect the method or field that declares the use of a parameterized type, you can see at runtime what type the paramerizable type was parameterized to. In short:

You cannot see on a type itself what type it is parameterized to a runtime, but you can see it in fields and methods where it is used and parameterized. Its concrete parameterizations in other words.

具体情况在页面上有说明