反射

10/19/2022 Java

摘要

JDK:1.8.0_202

# 一:类信息

Java程序中的许多对象在运行时都会出现两种类型:编译时类型运行时类型,例如代码:Person p=new Student(),这行代码将会生成一个p变量,该变量的编译时类型为Person,运行时类型为Student;除此之外,还有更极端的情形,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象运行时类型的方法。

为了解决这些问题,程序需要在运行时发现对象和类的真实信息。解决该问题有以下两种做法。

  1. 第一种做法是假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,可以先使用 instanceof 运算符进行判断,再利用强制类型转换将其转换成其运行时类型的变量即可。

  2. 第二种做法是编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。

# 1.1 获取 Class 对象

每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到 JVM 中的这个类。在Java程序中获得Class对象通常有如下三种方式。

  1. 使用Class类的 forName(String clazzName) 静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名);
  2. 调用某个类的class属性来获取该类对应的Class对象。例如,Person.class将会返回Person类对应的Class对象;
  3. 调用某个对象的 getClass() 方法。该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。

对于第一种方式和第二种方式都是直接根据类来取得该类的Class对象,相比之下,第二种方式有如下两种优势。

  1. 代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在;
  2. 程序性能更好。因为这种方式无须调用方法,所以性能更好。

# 1.2 从 Class 中获取信息

Class类提供了大量的实例方法来获取该 Class 对象所对应类的详细信息,Class 类大致包含如下方法,下面每个方法都可能包括多个重载的版本:

下面4个方法用于获取Class对应类所包含的构造器:

  • Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回此Class对象对应类的、带指定形参列表的public构造器。

  • Constructor<?>[] getConstructors() throws SecurityException:返回此Class对象对应类的所有public构造器。

  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回此Class对象对应类的、带指定形参列表的构造器,与构造器的访问权限无关。

  • Constructor<?>[] getDeclaredConstructors() throws SecurityException:返回此Class对象对应类的所有构造器,与构造器的访问权限无关。

下面4个方法用于获取Class对应类所包含的方法:

  • Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回此Class对象对应类的、带指定形参列表的public方法。

  • Method[] getMethods() throws SecurityException:返回此Class对象所表示的类的所有public方法。

  • Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回此Class对象对应类的、带指定形参列表的方法,与方法的访问权限无关。

  • Method[] getDeclaredMethods() throws SecurityException:返回此Class对象对应类的全部方法,与方法的访问权限无关。

如下4个方法用于访问Class对应类所包含的成员变量:

  • Field getField(String name) throws NoSuchFieldException, SecurityException:返回此Class对象对应类的、指定名称的public成员变量。

  • Field[] getFields() throws SecurityException:返回此Class对象对应类的所有public成员变量。

  • Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException:返回此Class对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。

  • Field[] getDeclaredFields() throws SecurityException:返回此Class对象对应类的全部成员变量,与成员变量的访问权限无关。

如下几个方法用于访问Class对应类上所包含的Annotation:

  • <A extends Annotation> A getAnnotation(Class<A> annotationClass):尝试获取该Class对象对应类上存在的、指定类型的Annotation;如果该类型的注解不存在,则返回null。

  • <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass):这是Java 8新增的方法,该方法尝试获取直接修饰该Class对象对应类的、指定类型的Annotation;如果该类型的注解不存在,则返回null。

  • Annotation[] getAnnotations():返回修饰该Class对象对应类上存在的所有Annotation。

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。

  • Annotation[] getDeclaredAnnotations():返回直接修饰该Class对应类的所有Annotation。

  • <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的getAnnotation() 方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取修饰该类的、指定类型的多个Annotation。

  • <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的 getDeclaredAnnotations() 方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定类型的多个Annotation。

如下方法用于访问该Class对象对应类包含的内部类:

  • Class<?>[] getDeclaredClasses() throws SecurityException:返回该Class对象对应类里包含的全部内部类。

如下方法用于访问该Class对象对应类所在的外部类:

  • Class<?> getDeclaringClass() throws SecurityException:返回该Class对象对应类所在的外部类。

如下方法用于访问该Class对象对应类所实现的接口:

  • Class<?>[] getInterfaces():返回该Class对象对应类所实现的全部接口。

如下几个方法用于访问该Class对象对应类所继承的父类:

  • native Class<? super T> getSuperclass():返回该 Class对象对应类的超类的Class对象。

如下方法用于获取Class对象对应类的修饰符、所在包、类名等基本信息:

  • native int getModifiers():返回此类或接口的所有修饰符。修饰符由 public、protected、private、final、static、abstract 等对应的常量组成,返回的整数应使用Modifier工具类的方法来解码,才可以获取真实的修饰符。

  • Package getPackage():获取此类的包。

  • String getName():以字符串形式返回此Class对象所表示的类的名称。

  • String getSimpleName():以字符串形式返回此Class对象所表示的类的简称。

除此之外,Class对象还可调用如下几个判断方法来判断该类是否为接口、枚举、注解类型等:

  • boolean isAnnotation():返回此Class对象是否表示一个注解类型(由@interface定义)。

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断此 Class 对象是否使用了Annotation修饰。

  • boolean isAnonymousClass():返回此Class对象是否是一个匿名类。

  • native boolean isArray():返回此Class对象是否表示一个数组类。

  • boolean isEnum():返回此Class对象是否表示一个枚举(由enum关键字定义)。

  • native boolean isInterface():返回此Class对象是否表示一个接口(使用interface定义)。

  • native boolean isInstance(Object obj):判断obj是否是此Class对象的实例,该方法可以完全代替 instanceof 操作符。

上面的多个 getMethod() 方法和 getConstructor() 方法中,都需要传入多个类型为 Class<?> 的参数,用于获取指定的方法或指定的构造器。关于这个参数的作用,假设某个类内包含如下三个info方法签名。

public void info()

public void info(String str)

public void info(String str, Integer num)

这三个同名方法属于重载,它们的方法名相同,但参数列表不同。在 Java 语言中要确定一个方法光有方法名是不行的,如果仅仅只指定info方法——实际上可以是上面三个方法中的任意一个!如果需要确定一个方法,则应该由方法名和形参列表来确定,但形参名没有任何实际意义,所以只能由形参类型来确定。例如想指定第二个info方法,则必须指定方法名为info,形参列表为String.class——因此在程序中获取该方法使用如下代码:

// 前一个参数指定方法名,后面的个数可变的 Class 参数指定形参类型列表
clazz.getMethod("info", String.class)
1
2

如果需要获取第三个 info 方法,则使用如下代码:clazz.getMethod("info", String.class, Integer.class)

获取构造器时无须传入构造器名——同一个类的所有构造器的名字都是相同的,所以要确定一个构造器只要指定形参列表即可。

import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

// 定义可重复注解
@Repeatable(Annos.class)
@interface Anno {
}


@Retention(value = RetentionPolicy.RUNTIME)
@interface Annos {
    Anno[] value();
}


// 使用 4 个注解修饰该类
@SuppressWarnings(value = "unchecked")
@Deprecated
// 使用重复注解修饰该类
@Anno
@Anno
public class ClassTest {

    // 为该类定义一个私有的构造器
    private ClassTest() {
    }

    // 定义一个有参数的构造器
    public ClassTest(String name) {
        System.out.println("执行有参数的构造器");
    }

    // 定义一个无参数的 info 方法
    public void info() {
        System.out.println("执行无参数的 info 方法");
    }

    // 定义一个有参数的 info 方法
    public void info(String str) {
        System.out.println("执行有参数的 info 方法,其 str 参数值:" + str);
    }

    // 定义一个测试用的内部类
    class Inner {
    }

    public static void main(String[] args) throws Exception {
        // 下面代码可以获取 ClassTest 对应的 Class
        Class<ClassTest> clazz = ClassTest.class;

        // 获取该 Class 对象所对应类的全部构造器
        Constructor<?>[] ctors = clazz.getDeclaredConstructors();
        System.out.println("ClassTest 的全部构造器如下:");
        for (Constructor<?> c : ctors) {
            System.out.println(c);
        }

        // 获取该 Class 对象所对应类的全部 public 构造器
        Constructor<?>[] publicCtors = clazz.getConstructors();
        System.out.println("ClassTest 的全部 public 构造器如下:");
        for (Constructor<?> c : publicCtors) {
            System.out.println(c);
        }

        // 获取该 Class 对象所对应类的全部 public 方法
        Method[] mtds = clazz.getMethods();
        System.out.println("ClassTest 的全部 public 方法如下:");
        for (Method md : mtds) {
            System.out.println(md);
        }

        // 获取该 Class 对象所对应类的指定方法
        System.out.println("ClassTest 里带一个字符串参数的 info 方法为:" + clazz.getMethod("info", String.class));

        // 获取该 Class 对象所对应类的全部注解
        Annotation[] anns = clazz.getAnnotations();
        System.out.println("ClassTest 的全部 Annotation 如下:");
        for (Annotation an : anns) {
            System.out.println(an);
        }
        System.out.println("该 Class 元素上的 @SuppressWarnings 注解为:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
        System.out.println("该 Class 元素上的 @Anno 注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class)));

        // 获取该 Class 对象所对应类的全部内部类
        Class<?>[] inners = clazz.getDeclaredClasses();
        System.out.println("ClassTest 的全部内部类如下:");
        for (Class<?> c : inners) {
            System.out.println(c);
        }

        // 使用 Class.forName() 方法加载 ClassTest 的 Inner 内部类
        Class<?> inClazz = Class.forName("top.qform.ClassTest$Inner");
        // 通过 getDeclaringClass() 访问该类所在的外部类
        System.out.println("inClazz 对应类的外部类为:" + inClazz.getDeclaringClass());
        System.out.println("ClassTest 的包为:" + clazz.getPackage());
        System.out.println("ClassTest 的父类为:" + clazz.getSuperclass());
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

虽然定义 ClassTest 类时使用了 @SuppressWarnings 注解,但程序运行时无法分析出该类里包含的该注解,这是因为 @SuppressWarnings 使用了 @Retention(value=SOURCE) 修饰,这表明 @SuppressWarnings 只能保存在源代码级别上,而通过 ClassTest.class 获取该类的运行时 Class 对象,所以程序无法访问到 @SuppressWarnings 注解。

对于只能在源代码上保留的注解,使用运行时获得的 Class 对象无法访问到该注解对象。

# 1.3 Java8 新增的方法参数反射

Java 8在java.lang.reflect包下新增了一个 Executable 抽象基类,该对象代表可执行的类成员,该类派生了 Constructor、Method 两个子类。

Executable 基类提供了大量方法来获取修饰该方法或构造器的注解信息;还提供了 isVarArgs() 方法用于判断该方法或构造器是否包含数量可变的形参,以及通过 getModifiers() 方法来获取该方法或构造器的修饰符。除此之外,Executable 提供了如下两个方法来获取该方法或参数的形参个数及形参名。

  • int getParameterCount():获取该构造器或方法的形参个数;
  • Parameter[] getParameters():获取该构造器或方法的所有形参。

上面第二个方法返回了一个 Parameter[] 数组,Parameter 也是Java 8新增的API,每个 Parameter 对象代表方法或构造器的一个参数。Parameter 也提供了大量方法来获取声明该参数的泛型信息,还提供了如下常用方法来获取参数信息。

  • int getModifiers():获取修饰该形参的修饰符;
  • String getName():获取形参名;
  • Type getParameterizedType():获取带泛型的形参类型;
  • Class<?> getType():获取形参类型;
  • boolean isNamePresent():该方法返回该类的class文件中是否包含了方法的形参名信息;
  • boolean isVarArgs():该方法用于判断该参数是否为个数可变的形参。

需要指出的是,使用javac命令编译Java源文件时,默认生成的class文件并不包含方法的形参名信息,因此调用 isNamePresent() 方法将会返回false,调用 getName() 方法也不能得到该参数的形参名。如果希望javac命令编译Java源文件时可以保留形参信息,则需要为该命令指定 -parameters 选项。

class Test {
    public void replace(String str, List<String> list) {
    }
}

public class MethodParameterTest {

    public static void main(String[] args) throws Exception {
        // 获取 String 的类
        Class<Test> clazz = Test.class;
        // 获取 String 类的带两个参数的 replace() 方法
        Method replace = clazz.getMethod("replace", String.class, List.class);
        // 获取指定方法的参数个数
        System.out.println("replace 方法参数个数:" + replace.getParameterCount());
        // 获取 replace 的所有参数信息
        Parameter[] parameters = replace.getParameters();
        int index = 1;
        // 遍历所有参数
        for (Parameter p : parameters) {
            if (p.isNamePresent()) {
                System.out.println("---第" + index++ + "个参数信息---");
                System.out.println("参数名:" + p.getName());
                System.out.println("形参类型:" + p.getType());
                System.out.println("泛型类型:" + p.getParameterizedType());
            }
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

如果现在 idea 编译添加 -parameters 可以查看 IDEA - 添加编译参数

# 二:反射操作对象

Class对象可以获得该类里的方法(由Method对象表示)、构造器(由Constructor对象表示)、成员变量(由Field对象表示),这三个类都位于 java.lang.reflect 包下,并实现了 java.lang.reflect.Member 接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的成员变量值。

# 2.1 创建对象

通过反射来生成对象有如下两种方式。

  1. 使用Class对象的 newInstance() 方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行 newInstance() 方法时实际上是利用默认构造器来创建该类的实例。

  2. 先使用Class对象获取指定的 Constructor 对象,再调用Constructor对象的 newInstance() 方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。

class Student {
    public String name;

    public Student() {

    }

    public Student(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return Optional.ofNullable(name).isPresent() ? "有参构造器获取的实例" : "无参构造器获取的实例";
    }
}

public class ObjectTest {

    public static void main(String[] args) throws Exception {
        // 方式二
        // 根据字符串来获取对应的 Class 对象
        Class<?> c1 = Class.forName("top.qform.reflect.Student");
        // 使用 Class 对应类的无参构造器获取实例
        Student s1 = (Student) c1.newInstance();
        System.out.println(s1);

        // 方式二
        // 需要如下三个步骤
        // 步骤一:获取该类的 Class 对象
        Class<?> c2 = Class.forName("top.qform.reflect.Student");
        // 步骤二:利用 Class 对象的 getConstructor() 方法来获取指定的构造器
        Constructor<?> ctor = c2.getConstructor(String.class);
        // 步骤三:调用 Constructor 的 newInstance() 方法来创建 Java 对象
        Student s2 = (Student) ctor.newInstance("test");
        System.out.println(s2);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 2.2 调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的 getMethods() 方法或者 getMethod() 方法来获取全部方法或指定方法——这两个方法的返回值是Method数组,或者Method对象。

每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法。在Method里包含一个 invoke() 方法,该方法的签名如下。

  • Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException:该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。
class Employer {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class MethodTest {

    public static void main(String[] args) throws Exception {
        Employer employer = new Employer();
        Class<?> clazz = employer.getClass();
        // 获取希望调用的 setter 方法
        Method mtd = clazz.getMethod("setName", String.class);
        // 通过 Method 的 invoke 方法执行 setter 方法
        mtd.invoke(employer, "test");
        // 调用 getter 方法
        System.out.println("getName返回值:" + clazz.getMethod("getName").invoke(employer));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

当通过 Method 的 invoke() 方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的 private 方法,则可以先调用Method对象的如下方法。

  • void setAccessible(boolean flag) throws SecurityException:将Method对象的 accessible 设置为指定的布尔值。值为true,指示该Method在使用时应该取消Java语言的访问权限检查;值为false,则指示该Method在使用时要实施Java语言的访问权限检查。

setAccessible() 方法并不属于Method,而是属于它的父类AccessibleObject。因此 Method、Constructor、Field 都可调用该方法,从而实现通过反射来调用private方法、private构造器和private成员变量。

# 2.3 访问成员变量

通过Class对象的 getFields()getField() 方法可以获取该类所包括的全部成员变量或指定成员变量。Field提供了如下两组方法来读取或设置成员变量值。

  • getXxx(Object obj):获取obj对象的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx。

  • setXxx(Object obj,Xxx val):将obj对象的该成员变量设置成val值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx。

class Person {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}


public class FieldTest {

    public static void main(String[] args) throws Exception {
        // 创建一个 Person 对象
        Person p = new Person();
        // 获取 Person 类对应的 Class 对象
        Class<Person> personClass = Person.class;
        // 获取 Person 的名为 name 的成员变量
        // 使用 getDeclaredField() 方法表明可获取各种访问控制符的成员变量
        Field nameField = personClass.getDeclaredField("name");
        // 设置通过反射访问该成员变量时取消访问权限检查
        nameField.setAccessible(true);
        // 调用 set() 方法为 p 对象的 name 成员变量设置值
        nameField.set(p, "张三");
        // 获取 Person 类名为 age 的成员变量
        Field ageField = personClass.getDeclaredField("age");
        // 设置通过反射访问该成员变量时取消访问权限检查
        ageField.setAccessible(true);
        // 调用 setInt() 方法为 p 对象的 age 成员变量设置值
        ageField.setInt(p, 30);
        System.out.println(p);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

getField() 方法只能获取public访问控制的成员变量,而 getDeclaredField() 方法则可以获取所有的成员变量

# 2.4 操作数组

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。Array提供了如下几类方法。

  • static Object newInstance(Class<?>componentType, int...length):创建一个具有指定的元素类型、指定维度的新数组。
  • static xxx getXxx(Object array, int index):返回array数组中第index个元素。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变为 get(Object array, int index)。
  • static void setXxx(Object array, int index, xxx val):将array数组中第 index 个元素的值设为val。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变成 set(Object array, int index, Object val)。
public class ArrayTest {

    public static void main(String[] args) throws Exception {
        // 创建一个元素类型为 String,长度为 10 的数组
        Object arr1 = Array.newInstance(String.class, 10);
        // 依次为 arr 数组中 index 为 5、6 的元素赋值
        Array.set(arr1, 5, "下标5");
        Array.set(arr1, 6, "下标6");
        // 取出 index 为 5 的元素的值
        Object i5 = Array.get(arr1, 5);
        System.out.println(i5);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.5 获取泛型信息

通过指定类对应的 Class 对象,可以获得该类里包含的所有成员变量,不管该成员变量是使用 private 修饰,还是使用 public 修饰。获得了成员变量对应的 Field 对象后,就可以很容易地获得该成员变量的数据类型,即使用如下代码即可获得指定成员变量的类型。

// 获取成员变量 f 的类型
Class<?> a = f.getType();

但这种方式只对普通类型的成员变量有效。如果该成员变量的类型是有泛型类型的类型,如 Map<String, Integer> 类型,则不能准确地得到该成员变量的泛型参数。为了获得指定成员变量的泛型类型,应先使用如下方法来获取该成员变量的泛型类型。

// 获取成员变量 f 的泛型类型
Type gType = f.getGenericType();

然后将 Type 对象强制类型转换为 ParameterizedType 对象,ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType 类提供了如下两个方法。

  • Type getRawType():返回没有泛型信息的原始类型;
  • Type[] getActualTypeArguments():返回泛型参数的类型。
public class GenericTest {

    private Map<String, Integer> score;

    public static void main(String[] args) throws Exception {
        Class<GenericTest> clazz = GenericTest.class;
        Field f = clazz.getDeclaredField("score");
        // 直接使用 getType() 取出类型只对普通类型的成员变量有效
        Class<?> a = f.getType();
        System.out.println("score的类型是:" + a);
        // 获取成员变量 f 的泛型类型
        Type gType = f.getGenericType();
        // 如果 gType 类型是 ParameterizedType 对象
        if (gType instanceof ParameterizedType) {
            // 强制类型转换
            ParameterizedType pType = (ParameterizedType) gType;
            // 获取原始类型
            Type rType = pType.getRawType();
            System.out.println("原始类型是:" + rType);
            // 取得泛型类型的泛型参数
            Type[] tArgs = pType.getActualTypeArguments();
            System.out.println("泛型信息是:");
            for (int i = 0; i < tArgs.length; i++) {
                System.out.println("第" + i + "个泛型类型是:" + tArgs[i]);
            }
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

从上面的运行结果可以看出,使用 getType() 方法只能获取普通类型的成员变量的数据类型;对于增加了泛型的成员变量,应该使用 getGenericType() 方法来取得其类型。

Type也是java.lang.reflect包下的一个接口,该接口代表所有类型的公共高级接口,Class是Type接口的实现类。Type包括原始类型、参数化类型、数组类型、类型变量和基本类型等。

# 三:参考文献

  • 《疯狂Java讲义(第三版) - 李刚》
最后更新: 10/20/2022, 9:14:55 PM