Java 反射

序言

  人类总是趋利避害的,我们会对微笑的人产生亲切感,对抱怨的人产生厌恶感。
  既然人类存在分辨思维,可以对不同的行为作出不同的反应,那么思考一个问题——程序具有思考决策的能力吗?
  世上唯一不变的就是变化,只有变化才能进化,进化才能适应未来,既然如此——程序应当具有思考决策能力。
  那么,作为 Java 程序,又是通过什么机制来实现这种能力的呢?
  反射而已。

什么是反射?

  在计算机科学中,反射是指计算机程序运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为

  要注意术语“反射”和“内省)”(type introspection)的关系。内省(或称“自省”)机制仅指程序在运行时对自身信息(称为元数据)的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。

  反射可以用于观察并修改程序在运行时的行为。一个面向反射的程序组件可以监测一个范围内的代码执行情况,可以根据期望的目标与此相关的范围修改本身。这通常是通过在运行时动态分配程序代码实现。

  在面向对象编程语言Java 中,官方文档的反射是这样解释的:

  Provides classes and interfaces for obtaining reflective information about classes and objects. Reflection allows programmatic access to information about the fields, methods and constructors of loaded classes, and the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

  翻译:提供用于获取有关类和对象的反射信息的类和接口。反射允许以编程方式访问相关已加载类的变量,方法和构造函数的相关信息,通过使用反射,可以对变量,方法和构造函数在安全限制内进行相关操作。

  反射是各种框架的灵魂,掌握反射可以让我们更好滴运用框架。

Class 对象

  在 Java 中其实有两种对象:

  • 实例对象:实例对象是类的实例,通常是通过new关键字构建的
  • Class 对象:Class 对象则是 JVM 生成用来保存对象的类的信息的

  Java 程序执行之前需要经过以下几个阶段:

  • 编译阶段:将源码文件编译为.class字节码文件,
  • 加载阶段:编译器会通过 JVM 内部的类加载机制将.class文件中的二进制数据加载进内存,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。在创建对象实例之前,JVM 会先检查 Class 对象是否在内存中存在:
    • 若不存在,则先加载 Class 对象,然后再创建对象实例;
    • 若存在,则直接根据 Class 对象创建对象实例。
    • 虽然 JVM 中只有一个 Class 对象,但可以根据 Class 对象生成多个对象实例。
  • 初始化阶段:初始化一些信息,如给静态变量赋初识值

  因此,Java 代码在计算机将经历 3 个阶段:

  • Source(源代码阶段)
  • Class(类对象阶段)
  • Runtime(运行时阶段)

  当我们写好 Java 代码后,.java文件将经过javac编译后生成字节码文件.class
  生成的字节码文件并不能直接让我们new一个对象,这个字节码文件中的二进制数据需经过类加载器加载进内存,在 JVM 中的方法区将形成一份描述该字节码的数据结构(存储了成员变量,构造方法和普通方法),JVM 的堆中将创建一个封装了这个数据结构的 Class 对象,通过该对象可以获知 Class 的数据结构信息。
  最后,我们可以通过这个 Class 对象的一些行为来创建一个真正的Person对象。  具体过程如下图:

3 个阶段

反射获取 Class 对象的 3 种方式

  Class 对象的获取有 3 种方式:

  • Class.forName()
  • 类名.class
  • 对象.getclass()(需要先创建对象)

  首先,我们先定义一个测试类,该类在cn.wk.basicjava.reflective包中:

1
2
3
4
5
6
package cn.wk.basicjava.reflective;
public class DemoClass {
static {
System.out.println("该类已经加载,链接,初始化");
}
}

① 通过 Class.forName(“类的全限定包名”)

  当执行Class.forName()时,JVM 会先检查Class对象是否装入内存:

  • 若没有装入内存,则先将 Class 对象装入内存,然后返回 Class 对象
  • 若已装入内存,则直接返回 Class 对象

  在加载 Class 对象后,会对类进行初始化,即执行类的静态代码块。
  此种方法多用于配置文件:将类名定义在配置文件中,读取文件,加载类。

1
2
3
4
5
@Test
public void testClassForName() throws ClassNotFoundException {
Class demo1 = Class.forName("cn.wk.basicjava.reflective.DemoClass");
System.out.println(demo1);
}

  运行结果:

1
2
该类已经加载,链接,初始化
class cn.wk.basicjava.reflective.DemoClass

注意哦:由于该类可能不存在,所以代码中需要抛出相应的ClassNotFoundException异常。

② 通过类名.class 方式

  当执行类名.class时,JVM 也会先检查Class对象是否装入内存:

  • 若没有装入内存,则先将 Class 对象装入内存,然后返回 Class 对象
  • 若已装入内存,则直接返回 Class 对象

  此种方式多用于参数的传递。

1
2
3
4
5
@Test
public void testClass() {
Class demo2 = DemoClass.class;
System.out.println(demo2);
}

  运行结果:

1
class cn.wk.basicjava.reflective.DemoClass

注意哦:此方法在加载 Class 对象后,并不会对 Class 对象进行初始化。因此输出结果和第一种及第三种方式不同。

③ 通过对象.getclass()(需要先创建对象)

  该方法在Object类中已定义,其使用的前提是对象已经创建好了
  相应的,由于对象已经创建,所以其肯定已经通过类加载机制加载、链接、初始化了。
  此种方式多用于让已创建的对象去获取字节码。

1
2
3
4
5
6
@Test
public void testGetClass() {
DemoClass demoClass = new DemoClass();
Class demo3 = demoClass.getClass();
System.out.println(demo3);
}

  运行结果:

1
2
该类已经加载,链接,初始化
class cn.wk.basicjava.reflective.DemoClass

注意哦:同一字节码(.class)文件在一次程序运行过程中,只会被加载一次,所以如果将 3 个变量比较的话,都会返回true

1
2
System.out.println(demo1==demo2);  // true
System.out.println(demo1==demo3); // true

反射获取 Class 对象的变量、构造器、方法

  通过 Class 对象的功能,可以通过反射调用该类的方法直接获取其:

  • 成员变量
  • 构造方法
  • 普通方法
  • 注解

  对获取的成员变量,Java 中使用Field类接收
  对获取的构造方法,Java 中使用Constuctor类接收
  对获取的普通方法,Java 中使用Method类接收。

  Java 中的 Class 类在java.lang包中,
  先定义一个Person类,该类在cn.wk.basicjava.reflective包中:

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
package cn.wk.basicjava.reflective;

public class Person {

public String name;
private int age;
private String sex;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

private Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public void eat() {
System.out.println("人吃食物");
}

public void eat(String what) {
System.out.println("人吃" + what);
}

@Override
public String toString() {
return "Person [name = " + name + ",age = " + age + ",sex = " + sex + "]";
}
}

获取成员变量

  使用Class类的相关方法可以获取成员变量:

  • Field[] getFields():获取所有public修饰的成员变量,返回一个Field数组
  • Field getField(String name):获取指定参数的public返修饰的成员变量,返回一个 Field对象
  • Field[] getDeclaredFields():获取所有成员变量,不论什么修饰符修饰的,返回一个Field数组
  • Field getDeclaredField(String name):获取指定参数的成员变量,不论什么修饰符修饰的,返回一个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
@Test
public void testGetField() throws Exception {
Class c = Class.forName("cn.wk.basicjava.reflective.Person");
Field[] fields = c.getFields();
System.out.println("********************");
System.out.println("只能获取public成员变量");
for (Field f : fields) {
System.out.println(f);
}

System.out.println("********************");
System.out.println("只能获取指定参数的public成员变量");
Field name = c.getField("name");
System.out.println(name);

System.out.println("********************");
System.out.println("获取所有成员变量,无论什么修饰符");
Field[] declaredFields = c.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}

System.out.println("********************");
System.out.println("获取指定参数的成员变量,无论什么修饰符");
Field age = c.getDeclaredField("age");
System.out.println(age);
}

  运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
********************
只能获取public成员变量
public java.lang.String cn.wk.basicjava.reflective.Person.name
public int cn.wk.basicjava.reflective.Person.age
********************
只能获取指定参数的public成员变量
public java.lang.String cn.wk.basicjava.reflective.Person.name
********************
获取所有成员变量,无论什么修饰符
public java.lang.String cn.wk.basicjava.reflective.Person.name
public int cn.wk.basicjava.reflective.Person.age
private java.lang.String cn.wk.basicjava.reflective.Person.sex
********************
获取指定参数的成员变量,无论什么修饰符
public int cn.wk.basicjava.reflective.Person.age

获取构造器及创建实例

  使用Class类的相关方法可以获取构造器,下面为部分方法:

  • Constructor<?>[] getConstructors():获取所有的public构造器,返回一个Constructor<?>数组
  • Constructor<T> getConstructor(Class<?>... parameterTypes):获取指定参数的public构造器,返回一个Constructor<T>
  • Constructor<?>[] getDeclaredConstructors():获取所有的构造器,无论什么修饰符修饰的,返回一个Constructor<?>数组
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取指定参数的构造器,无论什么修饰符修饰的,返回一个Constructor<T>类。

    ① 获取公有(public)构造器及创建实例

  举个栗子:通过反射获取 Class 对象的构造器并创建其实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testGetPersonConstructor() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class c = Class.forName("cn.wk.basicjava.reflective.Person");
//获取所有公共的构造方法
Constructor[] constructors = c.getConstructors();
for (Constructor ct : constructors){
System.out.println(ct);
}
System.out.println("\n*********************************************\n");
//获取无参数的公共的构造方法,抛出NoSuchMethodException异常
Constructor constructor1 = c.getConstructor();
System.out.println(constructor1);
//运行无参构造方法,创建实例
Object o1 = constructor1.newInstance();
System.out.println(o1);
//获取指定参数的公共的构造方法,抛出NoSuchMethodException异常
Constructor constructor2 = c.getConstructor(String.class, int.class);
System.out.println(constructor2);
//运行指定参数构造方法,创建实例
Object o2 = constructor2.newInstance("tom",18);
System.out.println(o2);
}

  运行结果:

1
2
3
4
5
6
7
8
9
public cn.wk.basicjava.reflective.Person(java.lang.String,int)
public cn.wk.basicjava.reflective.Person()

*********************************************

public cn.wk.basicjava.reflective.Person()
Person [name = null,age = 0,sex = null]
public cn.wk.basicjava.reflective.Person(java.lang.String,int)
Person [name = tom,age = 18,sex = null]

  当然,我们亦可快速获取构造方法的实例,前提是有无参权限为 public 的构造方法,此时不能传参.

1
2
3
4
5
6
@Test
public void testNoConstructor() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class c = Class.forName("cn.wk.basicjava.reflective.Person");
Object o = c.newInstance();
System.out.println(o);
}

  运行结果:

1
Person [name = null,age = 0,sex = null]

② 获取所有的构造器

1
2
3
4
5
6
7
8
9
@Test
public void getConstructors() throws ClassNotFoundException {
Class c = Class.forName("cn.wk.basicjava.reflective.Person");
System.out.println("获取所有的构造器,无论什么修饰符修饰的");
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}

  运行结果:

1
2
3
4
获取所有的构造器,无论什么修饰符修饰的
private cn.wk.basicjava.reflective.Person(java.lang.String,int,java.lang.String)
public cn.wk.basicjava.reflective.Person(java.lang.String,int)
public cn.wk.basicjava.reflective.Person()

③ 获取私有(private)构造器及创建实例(不推荐,破坏了程序的封装性,安全性)

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testPrivateConstructor() throws Exception {
Class c = Class.forName("cn.wk.basicjava.reflective.Person");
Constructor declaredConstructor = c.getDeclaredConstructor(String.class, int.class, String.class);

// 取消 Java 语言访问检查,默认 false
declaredConstructor.setAccessible(true);

Object o = declaredConstructor.newInstance("Jim", 20, "boy");
System.out.println(o);
}

  运行结果:

1
Person [name = Jim,age = 20,sex = boy]

获取成员方法并调用

  可以通过Class类的相关方法获取成员方法:

  • Method[] getMethods():获取所有的public修饰的成员方法,返回一个Method数组;
  • Method getMethod(String name, Class<?>... parameterTypes):获取指定参数的public修饰的成员方法,返回一个Method类;
  • Method[] getDeclaredMethods():返回所有的成员方法,无论什么修饰符修饰的,返回一个Method数组;;
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回指定参数的成员方法,无论什么修饰符修饰的,返回一个Method类;

  许多方法大同小异,下面就不列出所有情况了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testGetMethod() throws Exception {
Class c = Class.forName("cn.wk.basicjava.reflective.Person");
Method[] methods = c.getMethods();
for (Method m : methods) {
System.out.println(m);
}

// 创建实例
Object o = c.newInstance();
// 获得无参方法
Method eatMethod1 = c.getMethod("eat");
System.out.println(eatMethod1);
// 对带有指定参数(可以无参则)的指定对象调用由此 Method 对象表示的底层方法。
eatMethod1.invoke(o);

// 获得有参方法
Method eatMethod2 = c.getMethod("eat", String.class);
System.out.println(eatMethod2);
// 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
eatMethod2.invoke(o, "西瓜");
}

  运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public java.lang.String cn.wk.basicjava.reflective.Person.toString()
public void cn.wk.basicjava.reflective.Person.eat(java.lang.String)
public void cn.wk.basicjava.reflective.Person.eat()
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 final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
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()
public void cn.wk.basicjava.reflective.Person.eat()
人吃食物
public void cn.wk.basicjava.reflective.Person.eat(java.lang.String)
人吃西瓜

  因为Object类是所有类的父类,所有一并连它的方法也输出出来了。

Field、Constructor、Method 类

  FieldConstructorMethod这 3 个类都在 Java 的java.lang.reflect反射包中。
  下面列出这几个类的一些常用的方法:

Field 类

  Field类提供有关类或接口的单个变量的信息和动态访问,通过该类可以显示或设置变量的具体值。

  • Object get(Object obj):返回该所表示的字段的值 Field ,指定的对象上。
  • void set(Object obj, Object value):将指定对象参数上的此Field对象表示的字段设置为指定的新值。
  • void setAccessible(boolean flag):为true时忽略访问权限修饰符的安全检查,也叫暴力反射。

Constructor 类

  Constructor类提供了一个类的单个构造函数的信息和访问权限,通过该类可以创建实例对象。

  • T newInstance(Object... initargs):根据构造器参数创建实例对象
  • void setAccessible(boolean flag):为true时忽略访问权限修饰符的安全检查,也叫暴力反射。

Method 类

  Method类提供有关类和接口上单一方法的信息和访问权限,通过该方法可以获取方法的具体名,并且还可以通过invoke方法调用具体对象的具体方法。

  • String getName():获取具体的方法名
  • Object invoke(Object obj, Object... args)Object obj代表传入的对象,Object... args代表方法中的参数列表,可以无参,可以有一个参数,也可以有多个参数,所以可以无args参数。
  • void setAccessible(boolean flag):为true时忽略访问权限修饰符的安全检查,也叫暴力反射。

  invoke这个方法解释起来有点麻烦,因为一般情况下都是先创建对象,再通过该对象去调用它的方法(传入具体的参数),但通过反射获取的 Class 对象还未实例化,又如何调用呢?
  这就像枪未造出来,通过枪的设计图(Class 对象)肯定是不能装弹(方法参数)并射击(方法)的,只有当枪的实体创造出来(new出一个对象,即对象实例化)后,才能装弹并射击。
  但是,枪未创造出来就代表枪没有射击的功能(方法)了嘛?
  肯定是有的呀,一把枪不能射击还叫枪吗?枪的设计理念就是射击呀!所以说呢!我们可以先用设计图设计枪,等枪创造出来了再利用我们先前的理念用这把真真正正存在的枪进行装弹并射击。
  这就像马克思主义哲学的唯心主义——精神先于物质

相同点

  这 3 个类都可以调用setAccessible(boolean flag),参数为true时忽略访问权限修饰符的安全检查,可以访问private修饰的变量或构造器或方法,该方法其实是在AccessibleObject类中定义,只不过这 3 个类都直接或间接继承了它。

一个反射的例子

  首先定义一个配置文件p.properties

1
2
className=cn.wk.basicjava.reflective.Person
classMethod=eat

  之后通过读取配置文件并通过反射调用其方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
// 获取配置文件,将配置文件转换为 Properties 集合
InputStream resource = Demo.class.getClassLoader().getResourceAsStream("p.properties");
properties.load(resource);

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

// 通过反射将该类加载进内存
Class<?> c = Class.forName(className);
// 反射获取其方法
Method method = c.getMethod(classMethod);
Person person = new Person();
// 调用其方法
method.invoke(person);
}
}

注意

  由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
  另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

参考

文章信息

时间 说明
2019-03-14 初版
2021-10-12 增加序言部分
- 内容准备重构
0%