# 什么是反射和反射机制

反射其实就是把 java 类中的各个成分映射成一个个 java 对象。
即在运行状态中:
1. 对于任意一个类,能够知道这个类的所有属性和方法。
2. 对于任意一个对象,都能够调用它的任意一个属性和方法。
这种动态获取信息及动态调用对象的方法的功能就叫反射机制。

PS:java 中存在一个全局类,名字叫 Class 的类(常说的 class 是一个修饰类的关键字,这里说的 Class 是一个类名,不是关键字),这个 Class 类包含了所有的实例对象,通过操作这个 Class 类,我们就能够操控任意类。

# 为什么 Java 会有反射?

每一个事物的产生都有它的应用场景。在看到上面的定义的时候,初次学习反射的同学肯定是一头雾水的。
这里抛开概念,看看在不使用反射的时候是怎么写程序的。

image-20220918144759116

那如果用反射怎么写呢?如下:

image-20220918144809397

可以看到,代码量显著变大。但这里就有一个好处了,可以看到,我们一方面没有 import 这个 User 这个类,另一方面我们是用字符串来传入 User 这个类的,因此如果把 com.User.User 这个字符串放在外部,由传参或者配置文件给程序的话,程序就可以动态传入执行了。

Java 程序在运行起来的时候,首先是经过 javac 编译成 class 字节码,然后再由 jvm 去解析执行。java 还是一门静态类型语言,变量的类型在编译之前就需要提前确定,不然编译就通不过。这里就现在了程序的灵活性。比如上面的程序,实例化对象的类型是 User,如果我们想改成 User 的子类 WOMEN,就需要改代码重新编译。

介于静态语言的以上问题,很多静态语言就扩展出了反射机制,能够动态获取信息及动态调用对象方法。

# 反射 API

# java.lang.Class 类

这个 Class 类其实就是前面说的,由 JVM 在类加载的时候自动创建的,这个类的实例保存了对于类中的方法和属性等信息。

一般反射时进行的操作是获取要反射类的 Class 对象,从而获取类型元数据(metadata),比如字段、属性、构造器、方法等,获取后并调用。

  • forName 方法可以根据类的完全限定名称获取 Class 对象。会加载和连接,根据 initialize 参数决定是否初始化。(我们常用的不指定 initialize 就是默认初始化)
  • newInstance 创建此 Class 对象表示的类的新实例 内部实现是调用的 Constructor.newInstance 方法
  • getClasses 获取在此 Class 对象对应的类型中声明的 public 类或接口成员的 Class 对象数组,包括从超类继承的 public 类和接口成员
  • getDeclaredClasses 获取在此 Class 对象对应的类型中声明的类或接口成员的 Class 对象数组,包括 public, protected, default (package) access,private 类和接口,但不包括继承的类和接口
  • getFields 获取此 Class 对象表示的类或接口的所有可访问 public 字段 Field 数组 包括继承的
  • getDeclaredFields 获取此 Class 对象表示的类或接口的所有 public、protected、default(package)access 和 private 字段 Field 数组 不包括继承的
  • getField 获取此 Class 对象表示的类或接口的指定的可访问 public 字段 Field 对象 会递归向父类、父接口查
  • getDeclaredField 获取此 Class 对象表示的类或接口的指定的 public、protected、default(package)access 和 private 字段 Field 对象 不包括继承的
  • getMethods 获取该 class 对象表示的类或接口的所有 public 方法 Method 数组 包括继承的
  • getDeclaredMethods 获取该 class 对象表示的类或接口的 public, protected, default (package) access,private 方法 Method 数组 不包括继承的
  • getMethod 获取该 class 对象表示的类或接口的指定的 public 方法 Method 数组 会递归向父类、父接口查
  • getDeclaredMethod 获取该 class 对象表示的类或接口的指定的 public 方法 Method 数组 不包括继承的
  • getConstructors 获取这个 class 对象表示的类的所有 public 构造函数 Constructor 数组
  • getDeclaredConstructors 获取这个 class 对象表示的类的所有 public、protected、default(package)access 和 private 构造函数 Constructor 数组
  • getConstructor 获取这个 class 对象表示的类的指定的 public 构造函数 Constructor 对象
  • getDeclaredConstructor 获取这个 class 对象表示的类的指定的 public、protected、default(package)access 和 private 构造函数 Constructor 对象

# java.lang.reflect 包

java.lang.reflect 包提供了反射中用到类,主要的类说明如下:

Constructor

获取 Class 对象中的构造方法

  • getConstructor 获取当前类或父类单个公共构造方法
  • getConstructors 获取当前类或父类所有公共构造方法
  • getDeclaredConstructor 获取当前类单个构造方法
  • getDeclaredConstructors 获取当前类所有构造方法

Method

获取 Class 对象中的方法

  • getMethod 获取当前类或父类单个公共方法
  • getMethods 获取当前类或父类所有公共方法
  • getDeclaredMethod 获取当前类单个方法
  • getDeclaredMethods 获取当前类所有方法
  • invoke 执行指定对象 obj 中的 Method

Field

获取 Class 对象中的成员属性

  • getField 获取单个当前类或父类的 public 变量
  • getFields 获取当前类或父类的所有 public 变量
  • getDeclaredField 获取单个当前类的 public、private、protected 变量
  • getDeclaredFields 获取所有当前类的 public、private、protected 变量

# 使用反射

# Class 对象获取

首先,我们需要获取目标对象的一个 Class 对象。
Class 这个类的对象(实例)的获取方式主要有 4 种:
PS:通常只用 Class.forName。

image-20220918145409763

  • 第一种方法是通过类的全路径字符串获取 Class 对象,不需要事先 import 导入
  • 第二种方法有限制条件:需要导入类的包;
  • 第三种方法已经有了 new 了对象,不再需要反射。多此一举。
  • 第四种方法首先要获取个 classLoader 实例,之后再通过字符串获取 Class 对象。

# Class 对象实例获取

在成功获取到 Class 对象后,就需要操作这个对象,从而实现我们的目的了。而这首先就需要去获取对象的实例。
但在创建实例的时,由于类可能存在多个构造方法,因此在反射中针对不同的构造方法来创建实例的方式也是不一样的。
如图,目标类存在 3 种构造方法

image-20220918145620408

# 公有无参构造方法

Class.newInstance()

image-20220918145627471

# 公有含参构造方法(也可以调用无参构造器)

看这个方法名,就不难看出,这个是专门获取构造器的一个方法。因此它不局限于含参构造器。

Constructor  Class.getConstructor(Class<?>... parameterTypes)
Constructor[] getConstructors();

image-20220918145702046

# 私有含参构造方法(也可以调用公有构造方法)

需要注意的是:调用非 public 的 Constructor 时,必须首先通过 setAccessible (true) 设置私有变为公有。并且 setAccessible (true) 可能会失败。

Constructor getDeclaredConstructor(Class<?>... parameterTypes);
Constructor[] getDeclaredConstructors();

image-20220918150215894

# Class 对象成员方法获取

  • Method getMethod (name, Class…):获取某个 public 的 Method(包括父类)
  • Method getDeclaredMethod (name, Class…):获取当前类的某个 Method(不包括父类)
  • Method [] getMethods ():获取所有 public 的 Method(包括父类)
  • Method [] getDeclaredMethods ():获取当前类的所有 Method(不包括父类)

获取 Method 对象之后,可以调用如下函数:

1.getName ():返回方法名称,例如:”getScore”;

2.getReturnType ():返回方法返回值类型,也是一个 Class 实例,例如:String.class;

3.getParameterTypes ():返回方法的参数类型,是一个 Class 数组,例如:{String.class, int.class};

4.getModifiers ():返回方法的修饰符,它是一个 int,不同的 bit 表示不同的含义。

5.invoke(object,new Object {}) 调用执行方法

6.setAccessible () 设置 private 函数为 public 属性

# 获取公有成员方法

Method getMethod(name, Class) // 获取指定的方法
Method[] getMethods() // 获取所有公有成员方法

image-20220918150643604

image-20220918150650547

# 获取私有方法

Method getDeclaredMethod(name, Class…)
Method[] getDeclaredMethods() 

image-20220918150741347

# Class 对象成员变量获取

  • Field getField (name):根据字段名获取某个 public 的 field(包括父类)
  • Field getDeclaredField (name):根据字段名获取当前类的某个 field(不包括父类)
  • Field [] getFields ():获取所有 public 的 field(包括父类)
  • Field [] getDeclaredFields ():获取当前类的所有 field(不包括父类)

可以通过上面的四种方法获取成员变量对象,下面就是要对成员变量对象做一些操作,目前支持的函数为

  • 1.getName ():返回字段名称,例如,”name”;
  • 2.getType ():返回字段类型,也是一个 Class 实例,例如,String.class;
  • 3.getModifiers ():返回字段的修饰符,它是一个 int,不同的 bit 表示不同的含义。
  • 4.setAccessible (): 设置变量为 public
  • 5.set (): 设置字段值
  • 6.get ():获取字段值

针对如图例子进行测试:

image-20220918150806869

# 获取公有变量

Field Class.getField(name) // 获取指定的公有变量

image-20220918150939500

Field[] Class.getFields()// 获取所有公有变量

image-20220918151017300

# 获取私有变量

当目标成员变量为私有变量 (private) 时无法通过上述方法获取,需使用 getDeclaredFields 或 getDeclaredField 获取目标私有成员变量

并且访问私有成员变量时需要将 setAccessible 设置为 true, 关闭安全检查后才可获取私有成员变量内容

注:getDeclaredFields 或 getDeclaredField 并不是只用来获取 private 变量,public 变量同样也获取到

getDeclaredField 和 getField 区别

  • getDeclaredField 只可获取类本身 public private protected 成员属性
  • getField 可获取类本身及父类的 public 成员属性

PS:用起来其实就是多了个 setAccessible 操作,其他都是和正常的一样的

image-20220918151040398

image-20220918151104118

# 获取父类私有变量(方法也可以用这个思路去获取)

那么这里问题来了,如果我们想要获取父类的私有方法怎么办呢?其实解决方法比较简单,就是通过反射获取父类对应的 Class 对象。

Class<?> superclass = sonClass.getSuperclass();

获取 superclass 对象后,按照需求调用以下函数

1.Field getDeclaredField (name):根据字段名获取当前类的某个 field(不包括父类)

2.Method getDeclaredMethod (name, Class…):获取当前类的某个 Method(不包括父类)

3.setAccessible (): 设置变量为 public

其中 setAccessible 是必须被调用的为了修改其私有属性为 public

image-20220918151158754

image-20220918151202185

# 小例子:利用反射执行 Runtime

主要思路就是获取 Class 对象然后使用 java.lang.reflect 里提供的方法操作 Class 对象。

这里我们以 java.lang.runtime 为例。首先我们需要知道 runtime 有什么方法、是什么属性(公有私有)然后才能往下继续利用。

首先第一步是需要获取 Class 对象,即需要找到构造函数,构造实例,才能调用其他方法。

image-20220918151221536

image-20220918151226867

从源码我们可以看到,构造方法是私有方法,exec 是公有方法。因此我们可以使用如下代码来利用反射执行命令。

image-20220918151238451

再或者简短一点,因为 Runtime 类存在一个公有的 getRuntime 方法,能直接返回实例给我们。所以我们可以利用 getRuntime 这个方法直接一步到位,这样就跳过了私有构造器的获取。

image-20220918151246877