啥是 Java 的反射机制(reflection)?

Java 反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取信息和动态调用对象方法的功能称为 Java 的反射机制。

那为啥要整个 Java 的反射机制呢?

因为在第三方应用的开发过程中,经常会遇到某个类的方法或者属性是私有的或者只对系统开放,这时候无法直接调用,就可通过 Java 的反射机制来获取这些方法或者变量。

反射看起来破坏了封装性,可以让私有变量被外部访问到,类就不那么安全了。 Oracle 的官方文档中将发射的主要作用如下:

  1. 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,以实现一些扩展功能。
  2. 反射让开发人员可以枚举类的全部成员,包括构造函数、属性和方法等,以帮助开发人员写出正确的代码。
  3. 测试时可以利用反射 API 访问类的私有变量,以保证测试代码的覆盖率。

简言之,反射设计的初衷是作为开发者的一个工具,是用来帮助程序员实现一些本来不可能实现的功能的。如果没有反射,对于外部类的私有成员,我们将一筹莫展,所以我们有了反射这一后门,为程序设计提供了更大的灵活性。工具本身并没有错,关键在于如何正确地使用。

反射 API

反射 API 种类与功能

Java 类的成员包括三类:属性字段、构造函数和方法。而反射的 API 也与这些相关。如下所示

反射API分类

创建 demo

新建一个包,创建一个类。其中包含了需要测试的所有情况,私有属性、公有属性、公有构造方法、私有构造方法、公有方法和私有方法。

Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.reflection;

public class Student {

private String studentName; // 私用属性
public int studentAge; // 公用属性

public Student() { // 公有构造方法
}

private Student(String studentName) { // 私用构造方法
this.studentName = studentName;
}

public void setStudentAge(int studentAge) { // 公有方法
this.studentAge = studentAge;
}

private String show(String message) { // 私有方法
System.out.println("show: " + studentName + "," + studentAge + "," + message);
return "testReturnValue";
}
}

API 使用对象

以下都以 com.example.reflection.Student 作为蓝本对象。

获取 class 对象

获取 class 有以下三种方式。

1
2
3
4
5
6
7
8
9
// 通过字符串获取 class 对象,必须带上完整的包名和类名
Class studentClass = Class.forName("com.example.reflection.Student");

// 通过类的 class 属性
Class studentClass2 = Student.class;

// 通过对象的 getClass() 函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();

第一种方法是通过类的全路径完整包名去获取 class 对象。这也是平时最常用的反射获取 class 对象的方法。

第二种方法有限制,需要能带入类的包。

第三种方法已经有了现成的 class 的对象了,不再需要反射来获取了。

进一步观察,我们可以将上述三种方法的结果进行打印测试一下他们是否是同一个对象。

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.example.reflection.Student;

public class Main {
public static void main(String[] args) throws Exception{
// 通过字符串获取 class 对象,必须带上完整的包名和类名
Class studentClass = Class.forName("com.example.reflection.Student");

// 通过类的 class 属性
Class studentClass2 = Student.class;

// 通过对象的 getClass() 函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();

System.out.println("class1 = " + studentClass + "\n" +
"class2 = " + studentClass2 + "\n" +
"class3 = " + studentClass3 + "\n" +
"class1 == class2 ? " + (studentClass == studentClass2) + "\n" +
"class2 == class3 ? " + (studentClass2 == studentClass3));
}
}

运行结果如下:

1
2
3
4
5
6
PS D:\Users\Desktop\work> java Main
class1 = class com.example.reflection.Student
class2 = class com.example.reflection.Student
class3 = class com.example.reflection.Student
class1 == class2 ? true
class2 == class3 ? true

通过上述三种方法获取到的对象是同一个,也就是说 Java 在运行时,每个类指挥生成一个 class 对象。

获取到 class 对象之后就可以该怎么操作就怎么操作了。

获取成员变量

获取字段有两个 APIgetDeclaredFieldsgetFields 。前者用于获取所有声明的字段,包括公有的字段和私有的字段;后者仅仅用了获取公有字段。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Field;

System.out.println("\nall Fields declared:");
// 1.获取所有声明的字段
Field[] declaredFieldList = studentClass.getDeclaredFields();
for (Field declaredField : declaredFieldList) {
System.out.println("declared Field: " + declaredField);
}

System.out.println("\nall public Fields declared:");
// 2.获取所有公有的字段
Field[] fieldList = studentClass.getFields();
for (Field field : fieldList) {
System.out.println("field: " + field);
}

运行结果如下所示:

1
2
3
4
5
6
7
8
PS D:\Users\Desktop\work> java Main

all Fields declared:
declared Field: private java.lang.String com.example.reflection.Student.studentName
declared Field: public int com.example.reflection.Student.studentAge

all public Fields declared:
field: public int com.example.reflection.Student.studentAge

获取构造方法

获取构造方法同样包含两个 API :用于获取所有构造方法的 getDeclaredConstructors 和用于获取公有构造方法的 getConstructors

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Constructor;

System.out.println("\nall Constructors declared:");
// 1.获取所有声明的构造方法
Constructor[] declaredConstructorList = studentClass.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructorList) {
System.out.println("declared Constructor: " + declaredConstructor);
}

System.out.println("\nall public Constructors declared:");
// 2.获取所有公有的构造方法
Constructor[] constructorList = studentClass.getConstructors();
for (Constructor constructor : constructorList) {
System.out.println("constructor: " + constructor);
}

运行结果如下所示:

1
2
3
4
5
6
7
8
PS D:\Users\Desktop\work> java Main

all Constructors declared:
declared Constructor: public com.example.reflection.Student()
declared Constructor: private com.example.reflection.Student(java.lang.String)

all public Constructors declared:
constructor: public com.example.reflection.Student()

获取非构造方法

获取非构造方法的两个 API 是:获取所有声明的非构造函数的 getDeclaredMethods 和仅获取公有非构造函数的 getMethods

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Method;

System.out.println("\nall Methods declared:");
// 1.获取所有声明的函数
Method[] declaredMethodList = studentClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethodList) {
System.out.println("declared Method: " + declaredMethod);
}

System.out.println("\nall public Methods declared:");
// 2.获取所有公有的函数
Method[] methodList = studentClass.getMethods();
for (Method method : methodList) {
System.out.println("method: " + method);
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS D:\Users\Desktop\work> java Main

all Methods declared:
declared Method: private java.lang.String com.example.reflection.Student.show(java.lang.String)
declared Method: public void com.example.reflection.Student.setStudentAge(int)

all public Methods declared:
method: public void com.example.reflection.Student.setStudentAge(int)
method: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method: public final void java.lang.Object.wait() throws java.lang.InterruptedException
method: public boolean java.lang.Object.equals(java.lang.Object)
method: public java.lang.String java.lang.Object.toString()
method: public native int java.lang.Object.hashCode()
method: public final native java.lang.Class java.lang.Object.getClass()
method: public final native void java.lang.Object.notify()
method: public final native void java.lang.Object.notifyAll()

这里可以看到 getMethods 不仅获取到了自定义的公有方法,还可以获取到父类( Object )的公有方法。但是 getDeclaredMethods 无法获取到父类中的方法。

上述四种获取方法完整程序:

Main.java

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
import com.example.reflection.Student;

import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws Exception{
// 通过字符串获取 class 对象,必须带上完整的包名和类名
Class studentClass = Class.forName("com.example.reflection.Student");

// 通过类的 class 属性
Class studentClass2 = Student.class;

// 通过对象的 getClass() 函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();

System.out.println("class1 = " + studentClass + "\n" +
"class2 = " + studentClass2 + "\n" +
"class3 = " + studentClass3 + "\n" +
"class1 == class2 ? " + (studentClass == studentClass2) + "\n" +
"class2 == class3 ? " + (studentClass2 == studentClass3));

System.out.println("\nall Fields declared:");
// 1.获取所有声明的字段
Field[] declaredFieldList = studentClass.getDeclaredFields();
for (Field declaredField : declaredFieldList) {
System.out.println("declared Field: " + declaredField);
}
System.out.println("\nall public Fields declared:");
// 2.获取所有公有的字段
Field[] fieldList = studentClass.getFields();
for (Field field : fieldList) {
System.out.println("field: " + field);
}

System.out.println("\nall Constructors declared:");
// 1.获取所有声明的构造方法
Constructor[] declaredConstructorList = studentClass.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructorList) {
System.out.println("declared Constructor: " + declaredConstructor);
}
System.out.println("\nall public Constructors declared:");
// 2.获取所有公有的构造方法
Constructor[] constructorList = studentClass.getConstructors();
for (Constructor constructor : constructorList) {
System.out.println("constructor: " + constructor);
}

System.out.println("\nall Methods declared:");
// 1.获取所有声明的函数
Method[] declaredMethodList = studentClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethodList) {
System.out.println("declared Method: " + declaredMethod);
}
System.out.println("\nall public Methods declared:");
// 2.获取所有公有的函数
Method[] methodList = studentClass.getMethods();
for (Method method : methodList) {
System.out.println("method: " + method);
}

}
}

完整实现反射调用

直接贴代码如下,建议先自己手打再对比。

Main.java

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
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws Exception {
// 通过字符串获取 student class 对象
Class studentClass = Class.forName("com.example.reflection.Student");

// 前面已经知道了只有一个私有的构造方法,这里就直接调用,且也知道了其有一个 string 的参数,如果有多个就直接逗号分隔就好。
Constructor studentConstructor = studentClass.getDeclaredConstructor(String.class);

// 私有的构造方法则需要多下面这一行,动态获取权限,使之可以用。
studentConstructor.setAccessible(true);

// 使用构造方法类的 newInstance 方法来创建对象的实例,然后传入实际的参数,多个参数用逗号分隔即可
Object student = studentConstructor.newInstance("studentNameA");

// 获取声明的成员变量,通过变量的名字来获取,前面已经知道有哪些变量了,这里就展示获取 studentAge
Field studentAgeField = studentClass.getDeclaredField("studentAge");

// 私有成员变量同样需要 setAccessible(true) 来设置可用

// 使用成员变量类的 set 方法来设置字段值。第一个参数是实例对象,第二个参数是值
studentAgeField.set(student, 100);

// 获取声明的函数 第一个参数是函数名字,后续参数分别是该函数的形参类型,如有多个就以逗号分隔
Method studentShowMethod = studentClass.getDeclaredMethod("show", String.class);

// 私有的方法 同样也需要 setAccessible(true) 来设置可用
studentShowMethod.setAccessible(true);

// 使用函数类的 invoke 方法来调用函数, 第一个参数是实例对象,后续参数分别是函数的参数
Object result = studentShowMethod.invoke(student, "message");
System.out.println("result: " + result);
}
}

运行程序的输出结果如下所示,第二行输出是 Student.show() 的调用产生的。第三行则是 Main.java 打印的调用的返回结果。

1
2
3
PS D:\Users\Desktop\work> java Main
show: studentNameA,100,message
result: testReturnValue

若要查询更多方法或者 API 则可以查看参考链接一或者官方文档。

参考链接

参考链接一:(主要写了各种接口方法等) https://www.jianshu.com/p/9be58ee20dee

参考链接二: https://zhuanlan.zhihu.com/p/86293659