Java 反射
本小节我们来学习一个 Java 语言中较为深入的概念 —— 反射(reflection),很多小伙伴即便参与了工作,可能也极少用到 Java 反射机制,但是如果你想要开发一个 web 框架,反射是不可或缺的知识点。本小节我们将了解到 什么是反射,反射的使用场景,不得不提的 Class
类,如何通过反射访问类内部的字段、方法以及构造方法等知识点。
1. 什么是反射
Java 的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。反射被视为动态语言的关键。
通常情况下,我们想调用一个类内部的属性或方法,需要先实例化这个类,然后通过对象去调用类内部的属性和方法;通过 Java 的反射机制,我们就可以在程序的运行状态中,动态获取类的信息,注入类内部的属性和方法,完成对象的实例化等操作。
概念可能比较抽象,我们来看一下结合示意图看一下:
图中解释了两个问题:
- 程序运行状态中指的是什么时刻:
Hello.java
源代码文件经过编译得到Hello.class
字节码文件,想要运行这个程序,就要通过 JVM 的 ClassLoader (类加载器)加载Hello.class
,然后 JVM 来运行 Hello.class,程序的运行期间指的就是此刻; - 什么是反射,它有哪些功能:在程序运行期间,可以动态获得
Hello
类中的属性和方法、动态完成Hello
类的对象实例化等操作,这个功能就称为反射。
说到这里,大家可能觉得,在编写代码时直接通过 new
的方式就可以实例化一个对象,访问其属性和方法,为什么偏偏要绕个弯子,通过反射机制来进行这些操作呢?下面我们就来看一下反射的使用场景。
2. 反射的使用场景
Java 的反射机制,主要用来编写一些通用性较高的代码或者编写框架的时候使用。
通过反射的概念,我们可以知道,在程序的运行状态中,对于任意一个类,通过反射都可以动态获取其信息以及动态调用对象。
例如,很多框架都可以通过配置文件,来让开发者指定使用不同的类,开发者只需要关心配置,不需要关心代码的具体实现,具体实现都在框架的内部,通过反射就可以动态生成类的对象,调用这个类下面的一些方法。
下面的内容,我们将学习反射的相关 API
,在本小节的最后,我将分享一个自己实际开发中的反射案例。
3. 反射常用类概述
学习反射就需要了解反射相关的一些类,下面我们来看一下如下这几个类:
- Class:
Class
类的实例表示正在运行的Java
应用程序中的类和接口; - Constructor:关于类的单个构造方法的信息以及对它的权限访问;
- Field:Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限;
- Method:Method 提供关于类或接口上单独某个方法的信息。
字节码文件想要运行都是要被虚拟机加载的,每加载一种类,Java 虚拟机都会为其创建一个 Class
类型的实例,并关联起来。
例如,我们自定义了一个 ImoocStudent.java
类,类中包含有构造方法、成员属性、成员方法等:
public class ImoocStudent {
// 无参构造方法
public ImoocStudent() {
}
// 有参构造方法
public ImoocStudent(String nickname) {
this.nickname = nickname;
}
// 昵称
private String nickname;
// 定义getter和setter方法
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
源码文件 ImoocStudent.java
会被编译器编译成字节码文件 ImoocStudent.class
,当 Java 虚拟机加载这个 ImoocStudent.class
的时候,就会创建一个 Class
类型的实例对象:
Class cls = new Class(ImoocStudent);
JVM 为我们自动创建了这个类的对象实例,因此就可以获取类内部的构造方法、属性和方法等 ImoocStudent
的构造方法就称为 Constructor
,可以创建对象的实例,属性就称为 Field
,可以为属性赋值,方法就称为 Method
,可以执行方法。
4. Class 类
4.1 Class 类和 class 文件的关系
java.lang.Class
类用于表示一个类的字节码(.class)文件。
4.2 获取 Class 对象的方法
想要使用反射,就要获取某个 class
文件对应的 Class
对象,我们有 3 种方法:
- 类名.class:即通过一个
Class
的静态变量class
获取,实例如下:
Class cls = ImoocStudent.class;
- 对象.getClass ():前提是有该类的对象实例,该方法由
java.lang.Object
类提供,实例如下:
ImoocStudent imoocStudent = new ImoocStudent("小慕");
Class imoocStudent.getClass();
- Class.forName (“包名。类名”):如果知道一个类的完整包名,可以通过
Class
类的静态方法forName()
获得Class
对象,实例如下:
class cls = Class.forName("java.util.ArrayList");
4.3 实例
package com.imooc.reflect;
public class ImoocStudent {
// 无参构造方法
public ImoocStudent() {
}
// 有参构造方法
public ImoocStudent(String nickname) {
this.nickname = nickname;
}
// 昵称
private String nickname;
// 定义getter和setter方法
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public static void main(String[] args) throws ClassNotFoundException {
// 方法1:类名.class
Class cls1 = ImoocStudent.class;
// 方法2:对象.getClass()
ImoocStudent student = new ImoocStudent();
Class cls2 = student.getClass();
// 方法3:Class.forName("包名.类名")
Class cls3 = Class.forName("com.imooc.reflect.ImoocStudent");
}
}
代码中,我们在 com.imooc.reflect
包下定义了一个 ImoocStudent
类,并在主方法中,使用了 3 种方法获取 Class
的实例对象,其 forName()
方法会抛出一个 ClassNotFoundException
。
4.4 调用构造方法
获取了 Class
的实例对象,我们就可以获取 Contructor
对象,调用其构造方法了。
那么如何获得 Constructor
对象?Class
提供了以下几个方法来获取:
Constructor getConstructor(Class...)
:获取某个public
的构造方法;Constructor getDeclaredConstructor(Class...)
:获取某个构造方法;Constructor[] getConstructors()
:获取所有public
的构造方法;Constructor[] getDeclaredConstructors()
:获取所有构造方法。
通常我们调用类的构造方法,这样写的(以 StringBuilder
为例):
// 实例化StringBuilder对象
StringBuilder name = new StringBuilder("Hello Imooc");
通过反射,要先获取 Constructor
对象,再调用 Class.newInstance()
方法: