Java 反射

文章目录

尽管学校开设的 Java 程序设计课程已经结课,但是仍有些基础 Java 编程技术是老师没有讲授的,Java 反射机制便是其中之一。然而在使用开发框架时,反射机制不可或缺,因为框架的开发者需要根据框架的接口和注解来动态生成代码,而这些代码的生成过程依赖于反射机制。

“正射”

观察如下代码:

 1package com.jackgdn.entities;
 2
 3import lombok.Data;
 4
 5@Data
 6public class Person {
 7    public String name;
 8    private int age;
 9    private Gender gender;
10
11    public static enum Gender {
12        MALE, FEMALE, UNKNOWN
13    }
14
15    public Person() {
16        this.name = "Unknown";
17        this.age = -1;
18        this.gender = Gender.UNKNOWN;
19    }
20
21    public Person(String name, int age, Gender gender) {
22        this.name = name;
23        this.age = age;
24        this.gender = gender;
25    }
26
27    private int addAge(int value) {
28        this.age += value;
29        return this.age;
30    }
31}
 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4
 5public class Main {
 6    public static void main(String[] args) {
 7        Person john = new Person("John Doe", 20, Person.Gender.MALE);
 8        Person jane = new Person("Jane Doe", 19, Person.Gender.FEMALE);
 9        System.out.println(john.getName());
10        System.out.println(jane.getAge());
11    }
12}

在上述代码中,我们定义了一个 Person 类,并且在 Main 方法中创建可一个 Person 对象。我们在创建类时,已经只得到了这个类的方法、属性,因此可以直接创建类,这可以称为“正射”。而如果我们对一个类一无所知,则可以通过反射的方式来创建对象。

反射常用 API

反射的基础 Class 对象

在反射中,一切操作都从获取目标类的 Class 对象开始。

获取 Class 对象

获取一个 Class 对象有三种方法:

  1. 通过类的 class 属性获取(编译时已知类型)
  2. 通过对象的 getClass() 方法获取(运行时获取对象类型)
  3. 通过 Class.forName() 方法获取

以下是对三种方式的演示:

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4
 5public class Main {
 6    public static void main(String[] args) {
 7        // 通过类获取类对象
 8        Class<?> clazz1 = Person.class;
 9        System.out.println(clazz1.getName());
10
11        // 通过对象获取类对象
12        Person person = new Person();
13        Class<?> clazz2 = person.getClass();
14        System.out.println(clazz2.getName());
15
16        // 通过全限定名动态加载类
17        try {
18            Class<?> clazz3 = Class.forName("com.jackgdn.entities.Person");
19            System.out.println(clazz3.getName());
20        } catch (ClassNotFoundException e) {
21            e.printStackTrace();
22        }
23    }
24}
25
26/*
27 * output:
28 * com.jackgdn.entities.Person
29 * com.jackgdn.entities.Person
30 * com.jackgdn.entities.Person
31 */

Class 对象常用 API

 1package com.jackgdn;
 2
 3import java.util.Arrays;
 4
 5import com.jackgdn.entities.Person;
 6
 7public class Main {
 8    public static void main(String[] args) {
 9        Class<?> personClass = Person.class;
10
11        // 获取类的基本信息
12        System.out.println(personClass.getName()); // 输出类名
13        System.out.println(personClass.getSimpleName()); // 输出简单类名
14        System.out.println(personClass.getSuperclass().getName()); // 输出父类名
15        System.out.println(personClass.getPackage().getName()); // 输出包名
16
17        // 判断类的类型
18        System.out.println(personClass.isInterface()); // 是否为接口
19        System.out.println(personClass.isAnonymousClass()); // 是否为匿名类
20        System.out.println(personClass.isMemberClass()); // 是否为成员类
21
22        // 获取类的构造器、属性、方法
23        System.out.println(personClass.getConstructors().length); // 获取构造器数量
24        System.out.println(Arrays.toString(personClass.getFields())); // 获取公共属性
25        System.out.println(Arrays.toString(personClass.getDeclaredClasses())); // 获取所有属性数量
26        System.out.println(personClass.getMethods().length); // 获取方法数量
27        System.out.println(personClass.getDeclaredMethods().length); // 获取所有方法数量
28    }
29}
30
31/*
32 * output:
33 * com.jackgdn.entities.Person
34 * Person
35 * java.lang.Object
36 * com.jackgdn.entities
37 * false
38 * false
39 * false
40 * 2
41 * [public java.lang.String com.jackgdn.entities.Person.name]
42 * [class com.jackgdn.entities.Person$Gender]
43 * 15
44 * 11
45 */

操作字段:Field

Field 类表示类的属性/成员变量/字段,可用于获取或修改字段值。

获取 Field 对象

Field 对象需要通过 Class 对象获取,可以调用类对象的 getField 方法或者 getDeclaredField 方法获取字段对象,区别在于前者用于获取公共字段,而后者用于获取私有字段。

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4import java.lang.reflect.Field;
 5
 6public class Main {
 7    public static void main(String[] args) {
 8        Class<?> clazz = Person.class;
 9
10        try {
11            Field publicNameField = clazz.getField("name"); // 获取公共字段
12            Field privateAgeField = clazz.getDeclaredField("age"); // 获取私有字段
13            System.out.println(publicNameField.getName());
14            System.out.println(privateAgeField.getName());
15        } catch (NoSuchFieldException e) {
16            e.printStackTrace();
17        }
18    }
19}

通过 Field 对象访问或修改字段值

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4import java.lang.reflect.Field;
 5
 6public class Main {
 7    public static void main(String[] args) {
 8        Person person = new Person();
 9        Class<?> personClass = person.getClass();
10
11        try {
12            // 获取字段对象
13            Field nameField = personClass.getField("name");
14            Field ageField = personClass.getDeclaredField("age");
15
16            // 设置 name 字段值
17            nameField.set(person, "Jack");
18
19            // 设置 age 字段值。由于 age 是私有字段,需要先设置可访问
20            ageField.setAccessible(true);
21            ageField.set(person, 20);
22
23            // 获取字段值
24            System.out.println((String) nameField.get(person));
25            System.out.println((int) ageField.get(person));
26
27        } catch (NoSuchFieldException e) {
28            e.printStackTrace();
29        } catch (IllegalAccessException e) {
30            e.printStackTrace();
31        }
32    }
33}
34
35/*
36 * output:
37 * Jack
38 * 20
39 */

操作对象:Method

Method 表示类的方法,可用于动态调用方法。

获取 Method 对象

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4import java.lang.reflect.Method;
 5
 6public class Main {
 7    public static void main(String[] args) {
 8        Class<?> personClass = Person.class;
 9
10        try {
11            Method getAgeMethod = personClass.getMethod("getAge"); // 获取无参公有方法
12            Method setAgeMethod = personClass.getMethod("setAge", int.class); // 获取有参公有方法
13            Method addAgeMethod = personClass.getDeclaredMethod("addAge", int.class); // 获取有参私有方法
14
15            System.out.println(getAgeMethod.getName());
16            System.out.println(setAgeMethod.getName());
17            System.out.println(addAgeMethod.getName());
18        } catch (NoSuchMethodException e) {
19            e.printStackTrace();
20        }
21    }
22}
23
24/*
25 * output:
26 * getAge
27 * setAge
28 * addAge
29 */

注意

构造方法 Person 属于特殊方法,无法使用 getMethod 方法获取。

调用方法

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4
 5import java.lang.reflect.InvocationTargetException;
 6import java.lang.reflect.Method;
 7
 8public class Main {
 9    public static void main(String[] args) {
10        Person person = new Person();
11        Class<?> personClass = person.getClass();
12
13        try {
14            Method getAgeMethod = personClass.getMethod("getAge");
15            Method setAgeMethod = personClass.getMethod("setAge", int.class);
16            Method addAgeMethod = personClass.getDeclaredMethod("addAge", int.class);
17
18            setAgeMethod.invoke(person, 20); // 调用公有有参方法
19            System.out.println((int) getAgeMethod.invoke(person)); // 调用公有无参方法
20
21            addAgeMethod.setAccessible(true); // 设置私有方法可访问
22            System.out.println((int) addAgeMethod.invoke(person, 5)); // 调用私有有参方法
23        } catch (NoSuchMethodException e) {
24            e.printStackTrace();
25        } catch (IllegalAccessException e) {
26            e.printStackTrace();
27        } catch (InvocationTargetException e) {
28            e.printStackTrace();
29        }
30    }
31}
32
33/*
34 * output:
35 * 20
36 * 25
37 */

创建对象:Constructor

Constructor 表示类的构造器,可以用于动态创建对象。前面提到的不属于 Method 类型的构造方法,属于 Constructor 类型。

获取 Constructor 对象

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4import java.lang.reflect.Constructor;
 5
 6public class Main {
 7    public static void main(String[] args) {
 8        Class<?> personClass = Person.class;
 9
10        try {
11            Constructor<?> personConstructor = personClass.getConstructor(); // 获取无参构造器
12            Constructor<?> personConstructorWithParameters = personClass.getConstructor(String.class,
13                    int.class,
14                    Person.Gender.class); // 获取有参构造器
15
16            // 打印构造器名称及参数数量
17            System.out.printf("%s %d\n", personConstructor.getName(), personConstructor.getParameterCount());
18            System.out.printf("%s %d\n",
19                    personConstructorWithParameters.getName(),
20                    personConstructorWithParameters.getParameterCount());
21        } catch (NoSuchMethodException e) {
22            e.printStackTrace();
23        }
24    }
25}
26
27/*
28 * output:
29 * com.jackgdn.entities.Person 0
30 * com.jackgdn.entities.Person 3
31 */

通过构造器创建对象

 1package com.jackgdn;
 2
 3import com.jackgdn.entities.Person;
 4import java.lang.reflect.Constructor;
 5import java.lang.reflect.InvocationTargetException;
 6
 7public class Main {
 8    public static void main(String[] args) {
 9        Class<?> personClass = Person.class;
10
11        try {
12            Constructor<?> personConstructor = personClass.getConstructor(); // 获取无参构造器
13            Constructor<?> personConstructorWithParameters = personClass.getConstructor(String.class,
14                    int.class,
15                    Person.Gender.class); // 获取有参构造器
16
17            // 通过构造器创建对象
18            Person john = (Person) personConstructor.newInstance();
19            Person jane = (Person) personConstructorWithParameters.newInstance("Jane Doe", 19, Person.Gender.FEMALE);
20
21            System.out.println(john.getClass().getName());
22            System.out.println(jane.getAge());
23        } catch (NoSuchMethodException e) {
24            e.printStackTrace();
25        } catch (InstantiationException e) {
26            e.printStackTrace();
27        } catch (IllegalAccessException e) {
28            e.printStackTrace();
29        } catch (InvocationTargetException e) {
30            e.printStackTrace();
31        }
32    }
33}
34
35/*
36 * output:
37 * com.jackgdn.entities.Person
38 * 19
39 */

综合示例:动态操作类

我们使用上文中学习到的内容,创建并修改类。

 1package com.jackgdn;
 2
 3import java.lang.reflect.*;
 4
 5public class Main {
 6    public static void main(String[] args) {
 7        try {
 8            // 通过反射获取com.jackgdn.entities.Person类的Class对象
 9            Class<?> personClass = Class.forName("com.jackgdn.entities.Person");
10
11            // 获取Person类的无参构造方法
12            Constructor<?> perconConstructor = personClass.getConstructor();
13            // 通过无参构造方法创建Person类的实例对象
14            Object personObject = perconConstructor.newInstance();
15
16            // 获取Person类的age字段
17            Field ageField = personClass.getDeclaredField("age");
18            // 设置age字段可以被访问(即使是private)
19            ageField.setAccessible(true);
20            // 给personObject对象的age字段赋值为20
21            ageField.set(personObject, 20);
22
23            // 获取Person类的getAge方法
24            Method getAgeMethod = personClass.getMethod("getAge");
25            // 调用getAge方法并打印结果
26            System.out.println(getAgeMethod.invoke(personObject));
27
28            // 获取Person类的addAge方法(参数为int类型),并设置可访问
29            Method addAgeMethod = personClass.getDeclaredMethod("addAge", int.class);
30            addAgeMethod.setAccessible(true);
31            System.out.println(addAgeMethod.invoke(personObject, 5));
32        } catch (ClassNotFoundException | NoSuchMethodException
33                | InstantiationException | IllegalAccessException
34                | InvocationTargetException | NoSuchFieldException e) {
35            e.printStackTrace();
36        }
37    }
38}
39
40/*
41 * output:
42 * 20
43 * 25
44 */