# 什么是反射和反射机制
反射其实就是把 java 类中的各个成分映射成一个个 java 对象。
即在运行状态中:
1. 对于任意一个类,能够知道这个类的所有属性和方法。
2. 对于任意一个对象,都能够调用它的任意一个属性和方法。
这种动态获取信息及动态调用对象的方法的功能就叫反射机制。
PS:java 中存在一个全局类,名字叫 Class 的类(常说的 class 是一个修饰类的关键字,这里说的 Class 是一个类名,不是关键字),这个 Class 类包含了所有的实例对象,通过操作这个 Class 类,我们就能够操控任意类。
# 为什么 Java 会有反射?
每一个事物的产生都有它的应用场景。在看到上面的定义的时候,初次学习反射的同学肯定是一头雾水的。
这里抛开概念,看看在不使用反射的时候是怎么写程序的。
那如果用反射怎么写呢?如下:
可以看到,代码量显著变大。但这里就有一个好处了,可以看到,我们一方面没有 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。
- 第一种方法是通过类的全路径字符串获取 Class 对象,不需要事先 import 导入
- 第二种方法有限制条件:需要导入类的包;
- 第三种方法已经有了 new 了对象,不再需要反射。多此一举。
- 第四种方法首先要获取个 classLoader 实例,之后再通过字符串获取 Class 对象。
# Class 对象实例获取
在成功获取到 Class 对象后,就需要操作这个对象,从而实现我们的目的了。而这首先就需要去获取对象的实例。
但在创建实例的时,由于类可能存在多个构造方法,因此在反射中针对不同的构造方法来创建实例的方式也是不一样的。
如图,目标类存在 3 种构造方法
# 公有无参构造方法
Class.newInstance() |
# 公有含参构造方法(也可以调用无参构造器)
看这个方法名,就不难看出,这个是专门获取构造器的一个方法。因此它不局限于含参构造器。
Constructor Class.getConstructor(Class<?>... parameterTypes) | |
Constructor[] getConstructors(); |
# 私有含参构造方法(也可以调用公有构造方法)
需要注意的是:调用非 public 的 Constructor 时,必须首先通过 setAccessible (true) 设置私有变为公有。并且 setAccessible (true) 可能会失败。
Constructor getDeclaredConstructor(Class<?>... parameterTypes); | |
Constructor[] getDeclaredConstructors(); |
# 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() // 获取所有公有成员方法 |
# 获取私有方法
Method getDeclaredMethod(name, Class…)
Method[] getDeclaredMethods()
# 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 ():获取字段值
针对如图例子进行测试:
# 获取公有变量
Field Class.getField(name) // 获取指定的公有变量 |
Field[] Class.getFields()// 获取所有公有变量 |
# 获取私有变量
当目标成员变量为私有变量 (private) 时无法通过上述方法获取,需使用 getDeclaredFields 或 getDeclaredField 获取目标私有成员变量
并且访问私有成员变量时需要将 setAccessible 设置为 true, 关闭安全检查后才可获取私有成员变量内容
注:getDeclaredFields 或 getDeclaredField 并不是只用来获取 private 变量,public 变量同样也获取到
getDeclaredField 和 getField 区别
- getDeclaredField 只可获取类本身 public private protected 成员属性
- getField 可获取类本身及父类的 public 成员属性
PS:用起来其实就是多了个 setAccessible 操作,其他都是和正常的一样的
# 获取父类私有变量(方法也可以用这个思路去获取)
那么这里问题来了,如果我们想要获取父类的私有方法怎么办呢?其实解决方法比较简单,就是通过反射获取父类对应的 Class 对象。
Class<?> superclass = sonClass.getSuperclass(); |
获取 superclass 对象后,按照需求调用以下函数
1.Field getDeclaredField (name):根据字段名获取当前类的某个 field(不包括父类)
2.Method getDeclaredMethod (name, Class…):获取当前类的某个 Method(不包括父类)
3.setAccessible (): 设置变量为 public
其中 setAccessible 是必须被调用的为了修改其私有属性为 public
# 小例子:利用反射执行 Runtime
主要思路就是获取 Class 对象然后使用 java.lang.reflect 里提供的方法操作 Class 对象。
这里我们以 java.lang.runtime 为例。首先我们需要知道 runtime 有什么方法、是什么属性(公有私有)然后才能往下继续利用。
首先第一步是需要获取 Class 对象,即需要找到构造函数,构造实例,才能调用其他方法。
从源码我们可以看到,构造方法是私有方法,exec 是公有方法。因此我们可以使用如下代码来利用反射执行命令。
再或者简短一点,因为 Runtime 类存在一个公有的 getRuntime 方法,能直接返回实例给我们。所以我们可以利用 getRuntime 这个方法直接一步到位,这样就跳过了私有构造器的获取。