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
对象有三种方法:
- 通过类的
class
属性获取(编译时已知类型) - 通过对象的
getClass()
方法获取(运行时获取对象类型) - 通过
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 */