Java 中最为强大的技术莫过于反射技术,通过 Java 的反射机制,程序员可以更深入地控制程序的运行过程,如在程序运行时对用户输入的信息进行验证,还可以逆向控制程序的执行过程。此外,反射技术也被誉为 Java 框架设计的灵魂,很多优秀的开源框架都是通过反射机制来实现的。下面我们将详细介绍反射的基本用法,并通过一个案例来运用所学的知识。

一、基本原理

在介绍反射机制之前,先来介绍一下Java代码在计算机中经历的三个阶段,如下图所示:

Java代码在计算机中的三个阶段

  1. 当我们编写一段Java代码(如上图定义了一个Person类)之后,通过javac编译器编译之后生成字节码文件,这一阶段称之为源码阶段。

  2. 在生成字节码文件之后需要用类加载器(ClassLoader)将字节码文件加载进内存,在内存中会有一个Class类来描述字节码文件,Class类对象将所有的类抽象成由FieldConstructorMethod这三部分组成。

  3. 当创建好一个对象(如上图中通过 new Person() 构造方法创建了一个Person对象)之后便进入了最后一个阶段——运行时阶段。

由以上的内容可知,反射从本质上来说,就是将类的各个组成部分封装为Class类对象,并且可以在运行时通过Class类对象获取类的完整构造并调用对应的方法。

二、反射的用法

由前面介绍的内容可知,要想使用反射,首先需要获取类的Class类对象,之后从获取的Class类对象中获取类的相关属性并可以调用类方法。

1、获取反射中的Class对象

有三种方法可以获取类的Class类对象,下面通过一个例子来演示如何获取类的Class类对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
private String name;
private int age;
}

public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 可以用以下三种方法获取类的Class对象
// 1.直接获取类的字节码对象
Class personClass1 = Person.class;

// 2.根据类的全类名获取
Class personClass2 = Class.forName("my.study.reflect.Person");

// 3.根据已经实例化的对象获取
Person person = new Person();
Class personClass3 = person.getClass();
}
}

上面例子中给出的几种方法是有区别的,下面将通过表格来说明:

方法 说明
类名.class JVM将使用类装载器将类的字节码装入内存(前提是:类还没有装入内存),不做类的初始化工作。返回Class的对象
Class.forName(“全类名”) JVM装入类,并做类的静态初始化
实例化对象.getClass() 对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象所属的类的Class的对象

当我们编写一个新的java类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象。这就是生成Class对象的过程。

2、通过反射获取类对象的属性

(1) 获取成员变量

可通过下表给出的方法获取成员变量:

返回值 方法名(参数列表) 描述
Field getField(String name) 获取权限为public的指定成员变量
Field[] getFields() 获取所有权限为public的成员变量
Field getDeclaredField(String name) 获取指定的成员变量
Field[] getDeclaredFields() 获取所有的成员变量

注:getField方法和getFields方法只能获取权限为public的成员变量,而getDeclaredFieldgetDeclaredFields方法可以获取所有的成员变量,与权限修饰符无关。下面将通过一个例子来说明:

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
class Person {
private String name;
private int age;
protected int weight;
int height;
public String nickname;
public String hobby;
// 省略构造方法和成员方法
}

public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
// 1.获取权限为public的指定成员变量
Field nickname = personClass.getField("nickname");
System.out.println(nickname);
// 2.获取所有权限为public的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
// 3.获取指定的成员变量
Field age = personClass.getDeclaredField("age");
System.out.println(age);
// 4.获取所有的成员变量
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
public java.lang.String my.study.reflect.Person.nickname
--------------------------------------
public java.lang.String my.study.reflect.Person.nickname
public java.lang.String my.study.reflect.Person.hobby
--------------------------------------
private int my.study.reflect.Person.age
--------------------------------------
private java.lang.String my.study.reflect.Person.name
private int my.study.reflect.Person.age
protected int my.study.reflect.Person.weight
int my.study.reflect.Person.height
public java.lang.String my.study.reflect.Person.nickname
public java.lang.String my.study.reflect.Person.hobby
(2)获取构造方法

可通过下表给出的方法获取构造方法:

返回值 方法名(参数列表) 描述
Constructor getConstructor(Class<?>… parameterTypes) 获取权限为public的指定构造方法
Constructor<?>[] getConstructors() 获取所有权限为public的构造方法
Constructor getDeclaredConstructor(Class<?>… parameterTypes) 获取指定构造方法
Constructor<?>[] getDeclaredConstructors() 获取所有构造方法,按声明顺序返回

注:getConstructor方法和getConstructors方法只能获取权限为public的成员变量,而getDeclaredConstructorgetDeclaredConstructors方法可以获取所有的成员变量,与权限修饰符无关。下面将通过一个例子来说明:

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
class Person {

// 省略成员变量

// 无参构造方法
public Person () {}
// 带参数的构造方法
public Person (String name, int age) {
this.name = name;
this.age = age;
}

// 省略成员方法
}

public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class personClass = Class.forName("my.study.reflect.Person");
System.out.println("无参构造方法:");
// 获取无参构造方法
Constructor constructor1 = personClass.getConstructor();
System.out.println(constructor1);
System.out.println("带参数的构造方法:");
// 获取带参数的构造方法
Constructor constructor2 = personClass.getConstructor(String.class, int.class);
System.out.println(constructor2);
}
}

运行结果:

1
2
3
4
无参构造方法:
public my.study.reflect.Person()
带参数的构造方法:
public my.study.reflect.Person(java.lang.String,int)
(3)获取成员方法
返回值 方法名(参数列表) 描述
Method getMethod(String name, Class<?>… parameterTypes) 获取权限为public的指定方法
Method[] getMethods() 获取所有权限为public的方法
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 获取指定方法
Method[] getDeclaredMethods() 获取所有方法,按声明顺序返回

注:getMethod方法和getMethods方法只能获取权限为public的成员变量,而getDeclaredMethodgetDeclaredMethods方法可以获取所有的成员变量,与权限修饰符无关。下面将通过一个例子来说明:

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
class Person {

// 省略成员变量和构造方法

public void eat() {
System.out.println("eat...");
}

protected void run () {
System.out.println("run...");
}

void read () {
System.out.println("read...");
}

private void sleep () {
System.out.println("sleep...");
}
}

public class ReflectDemo {
public static void main(String[] args) throws Exception {
Person person = new Person();
Class personClass = person.getClass();
// 1.获取权限为public的指定方法
Method eat = personClass.getMethod("eat");
System.out.println(eat);
System.out.println("--------------------------------------");
// 2.获取所有权限为public的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("--------------------------------------");
// 3.获取指定方法
Method sleep = personClass.getDeclaredMethod("sleep");
System.out.println(sleep);
System.out.println("--------------------------------------");
// 4.获取所有方法
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void my.study.reflect.Person.eat()
--------------------------------------
public void my.study.reflect.Person.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
--------------------------------------
private void my.study.reflect.Person.sleep()
--------------------------------------
protected void my.study.reflect.Person.run()
void my.study.reflect.Person.read()
private void my.study.reflect.Person.sleep()
public void my.study.reflect.Person.eat()

注:getMethod方法和getDeclaredMethod方法支持获取重载的方法,只要在参数列表中传入方法名和参数类型即可。

若需获取更多关于Class类的API,请参阅:java.lang.Class

3、通过反射使用类对象

仅仅获取类的属性是毫无意义的,获取类的属性是为了使用属性的,下面将介绍如何使用反射来修改或调用类的属性。

(1)修改成员变量

对成员变量的控制需要通过Field类来进行,下面将通过一个例子来说明:

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
class Person {

private String name;
private int age;
protected int weight;
int height;
public String nickname;
public String hobby;

// 无参构造方法
public Person () {}
// 带参数的构造方法
public Person (String name, int age) {
this.name = name;
this.age = age;
}

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

public class ReflectDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Frank", 20);
Class personClass = person.getClass();
System.out.println("初始的Person对象: " + person.toString());
// 获取权限为private的成员变量age
Field age = personClass.getDeclaredField("age");
// 修改age的值,需要传入实例化的对象和要设置的值
age.set(person, 21);
System.out.println("修改后的Person对象: " + person.toString());
}
}

运行结果:

1
2
初始的Person对象: Person{name='Frank', age=20, weight=0, height=0, nickname='null', hobby='null'}
Exception in thread "main" java.lang.IllegalAccessException: Class my.study.reflect.ReflectDemo can not access a member of class my.study.reflect.Person with modifiers "private"

注意:如果直接修改权限为private 的成员变量会报错,因为直接修改会破坏类的封装性,如果想要修改权限为private 的成员变量值,需要对代码做以下修改:

1
2
3
4
5
6
7
// 获取权限为private的成员变量age
Field age = personClass.getDeclaredField("age");
// 要在修改值之前将反射对象的accessible标志设置为true,表示反射对象在使用时禁止检查Java语言访问控制
// 该方法也被称之为暴力反射
age.setAccessible(true)
// 修改age的值,需要传入实例化的对象和要设置的值
age.set(person, 21);

之后再次运行程序,结果如下:

1
2
初始的Person对象: Person{name='Frank', age=20, weight=0, height=0, nickname='null', hobby='null'}
修改后的Person对象: Person{name='Frank', age=21, weight=0, height=0, nickname='null', hobby='null'}

若需获取更多关于Field类的API,请参阅:java.lang.reflect.Field

(2)创建实例化对象

对构造方法的控制需要通过Constructor类来进行,下面将通过一个例子来说明:

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
class Person {

private String name;
private int age;

// 无参构造方法
public Person() {}

// 带参数的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}

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

public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 1.获取Class类对象
Class personClass = Person.class;
// 2.1 通过 类对象.newInstance() 方法创建实例
Person person = (Person) personClass.newInstance();
System.out.println("person: " + person.toString());
// 2.2 通过构造方法创建实例
Constructor constructor1 = personClass.getConstructor();
Person person1 = (Person) constructor1.newInstance();
System.out.println("person1: " + person1.toString());
Constructor constructor2 = personClass.getConstructor(String.class, int.class);
Person person2 = (Person) constructor2.newInstance("Frank", 20);
System.out.println("person2: " + person2.toString());
}
}

注:通过类对象直接创建实例只适用于无参构造方法,而通过Constructor类的newInstance方法可以使用带参数的构造方法。

运行结果:

1
2
3
person: Person{name='null', age=0}
person1: Person{name='null', age=0}
person2: Person{name='Frank', age=20}

若需获取更多关于Constructor类的API,请参阅:java.lang.reflect.Constructor

(3)调用成员方法

对成员方法的控制需要通过Method类来进行,下面将通过一个例子来说明:

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
class Person {

// 省略成员变量和构造函数

public void eat() {
System.out.println("eat...");
}

public int eat(String food) {
System.out.println("eat " + food);
return 1;
}
}

public class ReflectDemo {
public static void main(String[] args) throws Exception {
Person person = new Person();
Class personClass = person.getClass();
// 获取void eat()方法
Method eat1 = personClass.getMethod("eat");
// 调用方法,并返回Object类型的结果(该结果为被调用函数的返回值)
Object invoke1 = eat1.invoke(person);
System.out.println(invoke1);
// 获取int eat(String food)方法
Method eat2 = personClass.getMethod("eat", String.class);
// 调用方法,并返回Object类型的结果(该结果为被调用函数的返回值)
Object invoke2 = eat2.invoke(person, "bread");
System.out.println(invoke2);
}
}

运行结果:

1
2
3
4
eat...
null
eat bread
1

若需获取更多关于Method类的API,请参阅:java.lang.reflect.Method

三、案例

下面将通过一个简单的案例来了解反射在实际开发中的运用。

1、需求

假设需要写一个“框架”,可以创建任意类的对象并执行其中任意方法,那么又该如何去实现这个“框架”呢?

2、分析

显然,要想实现上述功能,就要用到反射机制,除此之外还要用到配置文件,可通过以下几个步骤来实现该功能:

  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载类文件进内存
  4. 创建对象
  5. 执行方法

3、实现

为实现该“框架”,需要编写一个框架类用来实现功能,除此之外还要定义两个类用来测试,此外还需要在项目src根目录下创建一个properties文件。以下是代码实现:

(1)FrameworkDemo.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
package my.study.reflect;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

class Person {
public void eat () {
System.out.println("person eat...");
}
}

class Animal {
public void sleep () {
System.out.println("animal sleep...");
}
}

public class FrameworkDemo {
public static void main(String[] args) throws Exception {
// 1.加载配置文件
// 1.1创建Properties对象
Properties properties = new Properties();
// 1.2加载配置文件,转换为一个集合
// 1.2.1获取class目录下的配置文件
ClassLoader classLoader = FrameworkDemo.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("application.properties");
properties.load(is);

// 2.获取配置文件中定义的数据
String className = properties.getProperty("className");
Object methodName = properties.get("methodName");

// 3.加载该类进内存
Class cls = Class.forName(className);
// 4.创建对象
Object object = cls.newInstance();
// 5.获取方法对象
Method method = cls.getMethod((String) methodName);
// 6.执行方法
method.invoke(object);
}
}

(2)application.properties的内容如下:

1
2
className=my.study.reflect.Person
methodName=eat

4、测试

(1)当className=my.study.reflect.PersonmethodName=eat时运行结果为:

1
person eat...

(2)当className=my.study.reflect.AnimalmethodName=sleep时运行结果为:

1
animal sleep...

5、总结

本案例实现的“框架”仅修改配置文件就可以实现创建任意类的实例并调用该实例的方法,而无需修改一行Java代码,这正是反射机制的强大之处。在实际应用中很多框架都是依靠反射和配置文件来实现的,因此学好反射对日后学习或者开发框架都是有很大的帮助的。