java · 7 3 月, 2023 0

java通过反射解析泛型类型

在工作中,我们时常需要使用泛型,最主要目的其实还是在于对公共逻辑的提取,以最大限度减少重复代码带来的复杂性和难以维护性。在一文读懂java泛型机制中对泛型做了基本概念的介绍,以及基础的使用。今天这篇文章主要是通过反射的方式获取泛型信息。

1. 反射

在java类型编译后的类信息都存放在Class对象中,在Class中包含了字段Field, 方法Method等定义原始信息,例如:

public class ClassTest {
    public static void main(String[] args) {
        Class<String> clazz = String.class;
        System.out.println(clazz.getName());

        String[] arr = {};
        Class<? extends String[]> arrClazz = arr.getClass();
        System.out.println(arrClazz.getName());
    }
}

执行以上代码,可以得到以下输出结果:

java.lang.String
[Ljava.lang.String;

Class对象本身也是泛型类型,我们看到获取到的Class对象带有泛型标识,同样通过泛型限制了Class中存储的部分数据类型。获取class对象有两种方式:

  • 根据类型调用.class可以获取到当前类型的Class对象
  • 根据实例对象调用.getClass()方法,获取Class对象

当我们获取到了Class对象之后,就可以通过Class对象操作Field, Method等信息,并获取字段Field的值和执行Method方法,这里就不做演示,有兴趣可以搜集下反射相关资料。

2. 反射泛型

一文读懂java泛型机制 一文中,谈到泛型包含了泛型类,泛型方法,泛型接口三种类型,因此我们就这三种类型泛型分别展开讨论,看如何通过泛型的方式获取实际的类型。

在反射中,最顶层的类型为Type, Type下又分为不同的实现类型,具体的结构体系如下:

每个类型的使用,会在后面的讲解中涉及到并讲解,因此每个类型的单独使用就不做介绍。

2.1 泛型类

定义一个泛型类型,代码如下:

public class GenericType<T>{
    private T value;

    public GenericType(T value) {
        this.value = value;
    }

    public GenericType() {}

    public T getValue() {
        return this.value;
    }

    public void setValue(T t) {
        this.value = t;
    }
}

我们对以上类型做测试,通过反射的方式查看获取到的泛型内容:

public class GenericTypeTest {
    public static void main(String[] args) throws NoSuchFieldException {
        Class<GenericType> genericTypeClass = GenericType.class;
        System.out.println(genericTypeClass);
        Type type = genericTypeClass.getGenericSuperclass();
        System.out.println(type); // Object

        GenericType<Integer> genericType = new GenericType<>();
        Class<? extends GenericType> aClass = genericType.getClass();
        type = aClass.getGenericSuperclass();
        System.out.println(type); // Object
    }
}

对应输出结果如下:

class java.lang.Object
class java.lang.Object

在上面的测试中,我们使用了getGenericSuperclass()获取泛型的超类,GenericType本身并没有集成或者实现其他类型,因此我们获取到的始终都是Object类型,这个时候我们就不能通过这种方式获取泛型信息,而是要使用getTypeParameters()方法获取类型上的泛型信息,测试代码如下:

// 获取参数化类型
TypeVariable<? extends Class<? extends GenericType>>[] typeParameters = aClass.getTypeParameters();
for (int i = 0; i < typeParameters.length; i++) {
    TypeVariable<? extends Class<? extends GenericType>> typeParameter = typeParameters[i];
    System.out.println("typeName: " + typeParameter.getTypeName());
    System.out.println("name: " + typeParameter.getName());
    System.out.println("bounds: " + typeParameter.getBounds());
    System.out.println("genericDeclaration: " + typeParameter.getGenericDeclaration());
    System.out.println("annotatedBounds: " + typeParameter.getAnnotatedBounds());
}

执行以上代码,我们得到如下输出结果:

typeName: T
name: T
bounds: [Ljava.lang.reflect.Type;@1b6d3586
genericDeclaration: class com.java.demo.generic.GenericType
annotatedBounds: [Ljava.lang.reflect.AnnotatedType;@74a14482

因此这里我们遇到了第一个TypeVariable类型的实现,这个类型中记录了泛型定义的原始信息,包括泛型的边界、泛型标识符、泛型声明的类型等。

这里对TypeVariable类型中的主要方法做一个晓得总结:

  • getTypeName()获取泛型标识的名称
  • getName()等同于getTypeName()方法
  • getBounds()获取泛型边界信息,如果没有明确声明上边界,默认为Object. 因为下边界最终也是Object
  • getGenericDeclaration()获取泛型声明类型
  • getAnnotatedBounds()该方法从1.8版本时候加入,用于获取注解类型的上界,如果没有明确声明上边界,默认为长度为0的数组

这里也许会有个疑问,在第二GenericType使用的时候,明确的声明了泛型类型为Type, 为什么最终还是获取不到Integer的泛型参数类型

这主要是因为泛型擦除,当我们实际在使用的时候指定泛型,在类型上使用泛型其实并不是一个新的类型,而是通过checkcast指令判断对应的值是否与泛型类型保持一致,因此以上的示例中通过泛型获取到的信息始终是Object类型的主要原因

继续探讨泛型类,我们为GenericType增加一个实现类,并明确指定泛型类型, 具体代码如下:

public class SubGenericType extends GenericType<Integer> {
}

对SubGenericType进行测试,测试代码如下:

public class SubGenericTypeTest {
    public static void main(String[] args) throws NoSuchFieldException {
        Class<SubGenericType> typeClass = SubGenericType.class;
        Type type = typeClass.getGenericSuperclass();
        System.out.println("isParameterizeType:" + (type instanceof ParameterizedType));
        System.out.println("type:" + type);

        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            System.out.println("rawType: " + parameterizedType.getRawType());
            Type[] actualParameters = parameterizedType.getActualTypeArguments();
            for (int i = 0; i < actualParameters.length; i++) {
                Type actualType = actualParameters[i];
                System.out.println("generic actualType:" + actualType);
            }

            System.out.println("getRawType:" + parameterizedType.getRawType());
            System.out.println("getOwnerType:" + parameterizedType.getOwnerType());

            Field value = ((Class<GenericType>) parameterizedType.getRawType()).getDeclaredField("value");
            System.out.println("Field.getType:" + value.getType());
            System.out.println("Field.getGenericType:" + value.getGenericType().getClass());
        }

        SubGenericType subGenericType = new SubGenericType();
        subGenericType.setValue(23); // setValue(Integer)
        Integer value = subGenericType.getValue(); // Integer
        System.out.println("value:" + value);
    }
}

以上代码执行结果输出如下:

isParameterizeType:true
type:com.java.demo.generic.GenericType<java.lang.Integer>
rawType: class com.java.demo.generic.GenericType
generic actualType:class java.lang.Integer
getRawType:class com.java.demo.generic.GenericType
getOwnerType:null
Field.getType:class java.lang.Object
Field.getGenericType:class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
value:23

我们通过类继承并指定泛型具体类型,通过getGenericSuperClass()方法获取到了ParameterizeType的类型,ParameterizeType的方法介绍如下:

  • getRawType()用于获取泛型定义的原始这类型,这里就是GenericType. 从ParameterizeType源码上来看,这里返回的一定是一个Class对象
  • getOwnerType()从源码来看,getOwnerType()实际上等同于Class.getDeclaringClass(), 这里返回的是null
  • getAcutalArguments()用于获取ParameterizeType中泛型的实际类型,在上面的例子中,我们获取到的是Integer类型

通过上面两个例子,我们基本上可以看到ParameterizeType和TypeVariable之间的一个区别:

  • 当泛型被真正指定为具体的类型的时候,返回的是ParameterizeType类型,这个时候可以通过该类型获取到真正的泛型类型
  • 当我们需要获取泛型定义、泛型边界信息时,则使用TypeVariable, 其中包含了详细的泛型信息

细心的小伙伴留意到了上面示例的最后几行代码,我们在定义的时候,实际上是没有重写父类GenericType中的方法,但是我们实际使用的时候,却能够直接调用setValue(Integer)这样的方法,我们反编译SubGenericeType类查看:

在反编译.class文件发现,也没有生成对应的桥接方法或者setValue(Integer)方法,那为什么可以直接调用setValue(Integer)方法呢?

我们反编译测试类的SubGenericTypeTest.class文件,对应的反编译信息如下:

我们从反编译中可以看出:

  • setValue()的时候,最终还是调用的SubGenericType#setValue(Object)方法,这个方法在调用的时候并不会报错,因为Integer本身就是Object的子类,这个本身是没有问题的。
  • getValue()也是使用的SubGenericeType#getValue(Object)方法,只是使用了checkcast类型转换的指令,以达到泛型类型验证的目的。

也许又有小伙伴有疑问,既然调用setValue(Object)方法,我可以设置setValue(“123”), 这个层面主要从编译器控制,保证传入的数据类型必须为Integer, 因此会导致编译错误

2.2 泛型接口

泛型接口其实和泛型类型相似,他们定义和使用的方式很多也是相同的。我们定义以下泛型接口:

public interface GenericInterface<T> {

    T getVal();

    void setVal(T val);
}

并定义以下两个实现类:

public class GenericInterfaceImpl implements GenericInterface<Integer> {

    private Integer val;

    @Override
    public Integer getVal() {
        return null;
    }

    @Override
    public void setVal(Integer val) {

    }

    public static void main(String[] args) {
        GenericInterface<String> genericInterface = new GenericInterfaceImpl2<>();
    }
}

class GenericInterfaceImpl2<T> implements GenericInterface<T> {

    private T val;

    @Override
    public T getVal() {
        return null;
    }

    @Override
    public void setVal(T val) {
        this.val = val;
    }


}

以上两个实现类,和我们泛型类的定义相似,GenericInterfaceImpl2这个本身也是一个泛型,并且将泛型传递给了GenericInterface接口,而GenericInterfaceImpl给出了实际的泛型Integer. 这里我们就不着重讲,直接对他们进行测试,并获取泛型的实际类型。

则对应的测试类如下:

public class GenericInterfaceTest {
    public static void main(String[] args) {
        Class<GenericInterface> genericInterfaceClass = GenericInterface.class;
        System.out.println("GenericInterface genericSuperClass: " + genericInterfaceClass.getGenericSuperclass()); // null
        TypeVariable<Class<GenericInterface>>[] typeParameters = genericInterfaceClass.getTypeParameters();
        for (int i = 0; i < typeParameters.length; i++) {
            TypeVariable<Class<GenericInterface>> typeParameter = typeParameters[i];
            System.out.println("GenericInterface TypeVariable:" + typeParameter.getName()); // T
        }

        Class<GenericInterfaceImpl2> impl2Class = GenericInterfaceImpl2.class;
        Type[] genericSuperclass = impl2Class.getGenericInterfaces();

        for (Type superclass : genericSuperclass) {
            System.out.println("GenericInterfaceImpl2 type: " + superclass.getClass()); // ParameterizeType
            if (superclass instanceof ParameterizedType) {
                ParameterizedType type = (ParameterizedType) superclass;
                Type[] actualTypeArguments = type.getActualTypeArguments();
                Arrays.stream(actualTypeArguments)
                                .forEach(argument -> {
                                    System.out.println("GenericInterfaceImpl2 actual type: " + argument.getTypeName());
                                    System.out.println("GenericInterfaceImpl2 actual type class: " + argument.getClass());
                                });

            }
        }

        Class<GenericInterfaceImpl> genericInterfaceClass1 = GenericInterfaceImpl.class;
        genericSuperclass = genericInterfaceClass1.getGenericInterfaces();

        for (Type superclass : genericSuperclass) {
            System.out.println("GenericInterfaceImpl type: " + superclass.getClass()); // ParameterizeType
            if (superclass instanceof ParameterizedType) {
                ParameterizedType type = (ParameterizedType) superclass;
                Type[] actualTypeArguments = type.getActualTypeArguments();
                Arrays.stream(actualTypeArguments)
                        .forEach(argument -> {
                            System.out.println("GenericInterfaceImpl actual type: " + argument.getTypeName());
                            System.out.println("GenericInterfaceImpl actual type class: " + argument.getClass());
                        });

            }
        }
    }
}

则对应的输出结果如下:

GenericInterface genericSuperClass: null
GenericInterface TypeVariable:T
GenericInterfaceImpl2 type: class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
GenericInterfaceImpl2 actual type: T
GenericInterfaceImpl2 actual type class: class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
GenericInterfaceImpl type: class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
GenericInterfaceImpl actual type: java.lang.Integer
GenericInterfaceImpl actual type class: class java.lang.Class

这里其实和泛型类展现保持一致,因此这里就不做过多的阐述。在上面接口定义中,我们定义了两个泛型方法,在GenericInterfaceImpl 中,因为明确指定了泛型的类型,因此在实现方法的时候,会将参数以及返回值替换成为具体的类型,这种方式本身是重写接口中的方法,并且会生成桥接方法,我们可以验证一下:

Method method = genericInterfaceClass1.getMethod("setVal", Object.class);
System.out.println("isBridge: " + method.isBridge());

则对应输出结果如下:

isBridge: true

2.3 泛型方法

泛型方法主要包含了两种:

  • 泛型信息记录在对应方法上
  • 泛型类中的实例泛型方法

还是以上面泛型接口中的两个类作为实例,查看实例中的泛型方法。实例方法中的泛型方法,泛型定义会随着实力的不同实现而有所不同。首先我们提取公共的方法,用于对方法的泛型化参数进行提取和处理:

/**
 * 输出泛型方法
 *
 * @param clazz  类型
 * @param method 方法
 * @param params 形参
 */
public static final void printGenericMethod(Class<?> clazz, String method, Class... params) {
    try {
        System.out.println("-----------------" + clazz.getName() + "#" + method + "--------------------------");
        Method targetMethod = clazz.getMethod(method, params);
        // 获取泛型参数
        System.out.println("method parameter start ++++++++++++++++++++++++");
        Type[] genericParameterTypes = targetMethod.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("type name: " + genericParameterType.getTypeName());
            System.out.println("type class:" + genericParameterType.getClass().getName());
            if (genericParameterType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    if (actualTypeArgument instanceof WildcardType) {
                        WildcardType wildcardType = (WildcardType) actualTypeArgument;
                        Type[] lowerBounds = wildcardType.getLowerBounds();
                        if (Objects.nonNull(lowerBounds)) {
                            for (Type lowerBound : lowerBounds) {
                                System.out.println("wildcard type lower bound: " + lowerBound.getTypeName());
                            }
                        }

                        Type[] upperBounds = wildcardType.getUpperBounds();
                        if (Objects.nonNull(upperBounds)) {
                            for (Type upperBound : upperBounds) {
                                System.out.println("wildcard type upper bound: " + upperBound.getTypeName());
                            }
                        }
                    } else {
                        System.out.println("ParameterizedType actual type: " + actualTypeArgument.getTypeName());
                    }

                }
            } else if (genericParameterType instanceof TypeVariable) {
                TypeVariable typeVariable = (TypeVariable) genericParameterType;
                Type[] bounds = typeVariable.getBounds();
                if (Objects.nonNull(bounds)) {
                    for (Type bound : bounds) {
                        System.out.println("TypeVariable bounds:" + bound.getTypeName());
                    }
                }
            } else {
                System.out.println("other type:" + genericParameterType.getClass().getName());
            }
        }
        System.out.println("method parameter end ++++++++++++++++++++++++");
        System.out.println("Method return type start+++++++++++++++++++");
        Type genericReturnType = targetMethod.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                if (actualTypeArgument instanceof WildcardType) {
                    WildcardType wildcardType = (WildcardType) actualTypeArgument;
                    Type[] lowerBounds = wildcardType.getLowerBounds();
                    if (Objects.nonNull(lowerBounds)) {
                        for (Type lowerBound : lowerBounds) {
                            System.out.println("wildcard return type lower bound: " + lowerBound.getTypeName());
                        }
                    }

                    Type[] upperBounds = wildcardType.getUpperBounds();
                    if (Objects.nonNull(upperBounds)) {
                        for (Type upperBound : upperBounds) {
                            System.out.println("wildcard return type upper bound: " + upperBound.getTypeName());
                        }
                    }
                } else {
                    System.out.println("ParameterizedType return actual type: " + actualTypeArgument.getTypeName());
                }
            }
        } else if (genericReturnType instanceof TypeVariable) {
            TypeVariable typeVariable = (TypeVariable) genericReturnType;
            Type[] bounds = typeVariable.getBounds();
            if (Objects.nonNull(bounds)) {
                for (Type bound : bounds) {
                    System.out.println("TypeVariable return bounds:" + bound.getTypeName());
                }
            }
        } else {
            System.out.println("other type:" + genericReturnType.getClass().getName());
        }
        System.out.println("Method return type end+++++++++++++++++++");
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}

这样就能够方便我们对泛型化方法进行处理,测试代码如下:

public static void main(String[] args) throws NoSuchMethodException {
    Class<GenericInterface> genericInterfaceClass = GenericInterface.class;
    // 获取setVal方法
    printGenericMethod(genericInterfaceClass, "getVal");
    printGenericMethod(genericInterfaceClass, "setVal", Object.class);

    System.out.println("==========================================================");

    Class<GenericInterfaceImpl2> impl2Class = GenericInterfaceImpl2.class;
    // 获取setVal方法
    printGenericMethod(impl2Class, "getVal");
    printGenericMethod(impl2Class, "setVal", Object.class);

    System.out.println("==========================================================");

    Class<GenericInterfaceImpl> genericInterfaceClass1 = GenericInterfaceImpl.class;
    // 获取setVal方法
    printGenericMethod(genericInterfaceClass1, "getVal");
    printGenericMethod(genericInterfaceClass1, "setVal", Integer.class);

}

输出结果如下:

-----------------com.java.demo.generic.GenericInterface#getVal--------------------------
method parameter start ++++++++++++++++++++++++
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
TypeVariable return bounds:java.lang.Object
Method return type end+++++++++++++++++++
-----------------com.java.demo.generic.GenericInterface#setVal--------------------------
method parameter start ++++++++++++++++++++++++
type name: T
type class:sun.reflect.generics.reflectiveObjects.TypeVariableImpl
TypeVariable bounds:java.lang.Object
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
other type:java.lang.Class
Method return type end+++++++++++++++++++
==========================================================
-----------------com.java.demo.generic.GenericInterfaceImpl2#getVal--------------------------
method parameter start ++++++++++++++++++++++++
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
TypeVariable return bounds:java.lang.Object
Method return type end+++++++++++++++++++
-----------------com.java.demo.generic.GenericInterfaceImpl2#setVal--------------------------
method parameter start ++++++++++++++++++++++++
type name: T
type class:sun.reflect.generics.reflectiveObjects.TypeVariableImpl
TypeVariable bounds:java.lang.Object
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
other type:java.lang.Class
Method return type end+++++++++++++++++++
==========================================================
-----------------com.java.demo.generic.GenericInterfaceImpl#getVal--------------------------
method parameter start ++++++++++++++++++++++++
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
other type:java.lang.Class
Method return type end+++++++++++++++++++
-----------------com.java.demo.generic.GenericInterfaceImpl#setVal--------------------------
method parameter start ++++++++++++++++++++++++
type name: java.lang.Integer
type class:java.lang.Class
other type:java.lang.Class
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
other type:java.lang.Class
Method return type end+++++++++++++++++++

对于方法的解析,基本原则也是和泛型类保持一致,明确有参数返回的时候,返回的是ParameterizedType类型,返回的是一个泛型标识的时候,则采用的是TypeVariable对象。这两个类的使用在泛型类中已经介绍的很清楚了,这里就不介绍了。

接下来我们主要看下WildcardType类型的使用,我们在GenericInterfaceImpl2中定义如下方法:

public Class<? extends Integer> transfer(Class<? super Integer> clazz) {
    return null;
}

这个方法本身没有实质性的意义,只是为了说明WildcardType的使用,

我们还是通过上面的方法,测试获取到的泛型信息:

printGenericMethod(impl2Class, "transfer", Class.class);

对应的输出结果如下:

-----------------com.java.demo.generic.GenericInterfaceImpl2#transfer--------------------------
method parameter start ++++++++++++++++++++++++
type name: java.lang.Class<? super java.lang.Integer>
type class:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
wildcard type lower bound: java.lang.Integer
wildcard type upper bound: java.lang.Object
method parameter end ++++++++++++++++++++++++
Method return type start+++++++++++++++++++
wildcard return type upper bound: java.lang.Integer
Method return type end+++++++++++++++++++

首先输出的是泛型化参数,泛型化参数定义我们使用的是<? super Integer>, 那么能够传入Integer, Number, Object的对象,当我们在解析上边界的时候,实际上解析成了两个:

  • 下边界为Integer
  • 上边界为Object

这个和我们分析上边界时,保持是一致的。

在解析返回泛型化参数的时候,返回结果定义为上边界,因此能够传入Integer以及Integer子类(这里不太恰当,Integer本身定义为final, 不会产生子类, 所以这里只能返回Integer)。所以在解析为上边界的时候,组中被解析为Integer.

2.4 GenericArrayType

另外一个泛型类型为GenericArrayType, 这里主要描述的是泛型的数组类型,下面我们通过测试代码测试一下:

public class GenericArrayTypeTest {
    public static void main(String[] args) {
        Class<GenericArrayTypeTest> clazz = GenericArrayTypeTest.class;
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if ("method".equals(method.getName())) {
                Type[] genericParameterTypes = method.getGenericParameterTypes();
                for (Type genericParameterType : genericParameterTypes) {
                    if (genericParameterType instanceof GenericArrayType) {
                        System.out.println("GenericArrayType ----> " + genericParameterType + "; genericComponentType ----> " + ((GenericArrayType) genericParameterType).getGenericComponentType());
                    } else {
                        System.out.println("Not GenericArrayType ----> " + genericParameterType);
                    }
                }
            }
        }
    }

    public static <T> void method(String[] strings, List<String> ls, List<String>[] lsa, T[] ts, List<T>[] tla, T[][] tts) {

    }
}

通过上面的测试可以得到一下结论:

  • 该类只是针对泛型数组,集合类型并不能解析为GenericArrayType
  • 对于二位数组,最终解析的类型为T[]
Not GenericArrayType ----> class [Ljava.lang.String;
Not GenericArrayType ----> java.util.List<java.lang.String>
GenericArrayType ----> java.util.List<java.lang.String>[]; genericComponentType ----> java.util.List<java.lang.String>
GenericArrayType ----> T[]; genericComponentType ----> T
GenericArrayType ----> java.util.List<T>[]; genericComponentType ----> java.util.List<T>
GenericArrayType ----> T[][]; genericComponentType ----> T[]

参考文章

https://www.cnblogs.com/throwable/p/12315988.html

 

以上就是关于泛型的反射解析全部内容,如果有任何问题,欢迎留言区评论。