跳到主要内容

Java 反射

本小节我们来学习一个 Java 语言中较为深入的概念 —— 反射(reflection),很多小伙伴即便参与了工作,可能也极少用到 Java 反射机制,但是如果你想要开发一个 web 框架,反射是不可或缺的知识点。本小节我们将了解到 什么是反射反射的使用场景,不得不提的 Class 类,如何通过反射访问类内部的字段、方法以及构造方法等知识点。

1. 什么是反射

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

通常情况下,我们想调用一个类内部的属性或方法,需要先实例化这个类,然后通过对象去调用类内部的属性和方法;通过 Java 的反射机制,我们就可以在程序的运行状态中,动态获取类的信息,注入类内部的属性和方法,完成对象的实例化等操作。

概念可能比较抽象,我们来看一下结合示意图看一下:

图中解释了两个问题:

  1. 程序运行状态中指的是什么时刻Hello.java 源代码文件经过编译得到 Hello.class 字节码文件,想要运行这个程序,就要通过 JVM 的 ClassLoader (类加载器)加载 Hello.class,然后 JVM 来运行 Hello.class,程序的运行期间指的就是此刻;
  2. 什么是反射,它有哪些功能:在程序运行期间,可以动态获得 Hello 类中的属性和方法、动态完成 Hello 类的对象实例化等操作,这个功能就称为反射。

说到这里,大家可能觉得,在编写代码时直接通过 new 的方式就可以实例化一个对象,访问其属性和方法,为什么偏偏要绕个弯子,通过反射机制来进行这些操作呢?下面我们就来看一下反射的使用场景。

2. 反射的使用场景

Java 的反射机制,主要用来编写一些通用性较高的代码或者编写框架的时候使用。

通过反射的概念,我们可以知道,在程序的运行状态中,对于任意一个类,通过反射都可以动态获取其信息以及动态调用对象。

例如,很多框架都可以通过配置文件,来让开发者指定使用不同的类,开发者只需要关心配置,不需要关心代码的具体实现,具体实现都在框架的内部,通过反射就可以动态生成类的对象,调用这个类下面的一些方法。

下面的内容,我们将学习反射的相关 API,在本小节的最后,我将分享一个自己实际开发中的反射案例。

3. 反射常用类概述

学习反射就需要了解反射相关的一些类,下面我们来看一下如下这几个类:

  • ClassClass 类的实例表示正在运行的 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 种方法:

  1. 类名.class:即通过一个 Class 的静态变量 class 获取,实例如下:
Class cls = ImoocStudent.class;

  1. 对象.getClass ():前提是有该类的对象实例,该方法由 java.lang.Object 类提供,实例如下:
ImoocStudent imoocStudent = new ImoocStudent("小慕");
Class imoocStudent.getClass();

  1. 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() 方法: