反射知识点总汇
共 11332字,需浏览 23分钟
·
2021-10-20 08:08
点击上方“程序员大白”,选择“星标”公众号
重磅干货,第一时间送达
一、概念
1.1 概念
简单说,JAVA反射机制是指在运行态可直接操作任意类或对象的所有属性和方法的功能。
1.2 反射的用途
在运行时获取任意对象所属的类
Class> clazz = Class.forName(String className);
在运行时构造任意类的对象
Object obj = clazz.newInstance();
在运行时获取任意类所具有的成员变量和方法
field.set(Object obj, Object value);
field.get(Object obj);
在运行时调用任意对象的方法 (最常见的需求,尤其是当该方法是私有方法或者隐藏方法)
method.invoke(Object obj, Object... args);
反射还可以获取类的其他信息,包含modifiers(下面会介绍),以及superclass, 实现的interfaces等。
针对动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。反射机制在运行时只能调用methods或改变fields内容,却无法修改程序结构或变量类型。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
二、反射
2.1 核心类
java.lang.Class:代表类
java.lang.reflect.Constructor: 代表类的构造方法
java.lang.reflect.Field: 代表类的属性
java.lang.reflect.Method: 代表类的方法
java.lang.reflect.Modifier:代表类、方法、属性的描述修饰符。
其中Modifier取值范围如下:
public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfp, interface。
Constructor, Field, Method这三个类都继承AccessibleObject,该对象有一个非常重要的方法setAccessible(boolean flag)
, 借助该方法,能直接调用非Public的属性与方法。
2.2 核心方法
1.成员属性(Field):
1getFields():获得类的public类型的属性。
2getDeclaredFields():获得类的所有属性。
3getField(String name)
4getDeclaredField(String name):获取类的特定属性
2.成员方法(Method):
1getMethods():获得类的public类型的方法。
2getDeclaredMethods():获得类的所有方法。
3getMethod(String name, Class[] parameterTypes):获得类的特定方法
4getDeclaredMethod(String name, Class[] parameterTypes):获得类的特定方法
3.构造方法(Constructor):
1getConstructors():获得类的public类型的构造方法。
2getDeclaredConstructors():获得类的所有构造方法。
3getConstructor(Class[] parameterTypes):获得类的特定构造方法
4getDeclaredConstructor(Class[] params);获得类的特定方法
2.3 深入Class类
Java所有的类都是继承于Oject类,其内声明了多个应该被所有Java类覆写的方法:hashCode()、equals()、clone()、toString()、notify()、wait()、getClass()等,其中getClass返回的便是一个Class类的对象。Class类也同样是继承Object类,拥有相应的方法。
Java程序在运行时,运行时系统对每一个对象都有一项类型标识,用于记录对象所属的类。虚拟机使用运行时类型来选择相应方法去执行,保存所有对象类型信息的类便是Class类。
Class类没有公共构造方法,Class对象是在加载类时由 Java 虚拟机以及通过调用ClassLoader的defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。
三、用处
1.如何通过反射获取一个类?
Class.forName(String className); (最常用)
2.如何调用私有类,或者类的私有方法或属性?
私有类:通过getDeclaredConstructor获取constructor,再调用constructor.setAccessible(true);
私有方法:通过getDeclaredMethod获取method,再调用method.setAccessible(true);
私有属性:通过getDeclaredField获取field,再调用field.setAccessible(true);
3.@hide标记是做什么的,反射能否调用@hide标记的类?
在Android的源码中,我们会发现有很多被”@hide”标记的类,它的作用是使这个类或方法在生成SDK时不可见。那么应用程序便不可以直接调用。而反射机制可调用@hide标记的类或方法,如入无人之地,畅通无阻。
4.如何通过反射调用内部类?
假设com.reflect.Outer类有一个内部类inner,调用方法如下:
1String className = "com.reflect.Outer$inner";
2Class.forName(className);
五、反射用法
前面介绍到,反射是为了在运行态能操作类和对象,接下来重点介绍如何反射使用。
对于正常方式来调用方法,往往只需要一行到两行代码,即可完成相应工作。而反射则显得比较繁琐,之所以繁琐仍然使用反射方式,是因为反射能干很多正常实例化对象的方式所无法做到的事。比如操作那些private的类、方法、属性,以及@hide标记过的类、方法、属性。
为了到达即能有反射的功效,同时调用方法简单易用,写了一个ReflectUtils
类。对于方法调用,与正常对象的调用过程差不多。主要有以下4类需要用到反射的地方:
调用类的静态方法
调用类的非静态方法
set/get类的静态属性
set/get类的非静态属性
5.1 ReflectUtils
类用法
调用流程一般为先获取类或对象,再调用相应方法。针对上述4种需求,用法分别如下:
1. 调用类的静态方法
对于参数方法,只需把参数,紧跟方法名后面,可以跟不定长的参数个数。
1Class> clazz = ReflectUtils.getClazz("com.yuanhh.model.Outer"); //获取class
2ReflectUtils.invokeStaticMethod(clazz, "outerStaticMethod"); //无参方法
3ReflectUtils.invokeStaticMethod(clazz, "outerStaticMethod","yuanhh"); //有参数方法
2. 调用类的非静态方法
1Object obj = ReflectUtils.newInstance("com.yuanhh.model.Outer"); //实例化对象
2ReflectUtils.invokeMethod(obj, "outerMethod"); //无参方法
3ReflectUtils.invokeMethod(obj, "outerMethod", "yuanhh"); //有参方法 **3. set/get类的静态属性**
4
5ReflectUtils.getStaticField(clazz, "outerStaticField"); //get操作
6ReflectUtils.setStaticField(clazz, "outerStaticField", "new value"); //set操作
4. set/get类的非静态属性
1ReflectUtils.getField(obj, "outerField"); //get操作
2ReflectUtils.setField(obj, "outerField", "new value"); //set操作
如果只知道类名,需先查看该类的所有方法详细参数信息,可以通过调用dumpClass(String className)
,返回值是String,记录着所有构造函数,成员方法,属性值的信息。
5.2 核心代码
关于ReflectUtils
类,列表部分核心方法。
先定义一个Outer
类, 包名假设为com.yuanhh.model
,对于该类,非public,构造方法,成员方法,属性都是private:
1class Outer {
2 private String outerField = "outer Field";
3 private static String outerStaticField = "outer static Field";
4
5 private Outer(){
6 System.out.println("I'am outer construction without args");
7 }
8
9 private Outer(String outerField){
10 this.outerField = outerField;
11 System.out.println("I'am outer construction with args "+ this.outerField);
12 }
13
14 private void outerMethod(){
15 System.out.println("I'am outer method");
16 }
17
18 private void outerMethod(String param){
19 System.out.println("I'am outer method with param "+param);
20 }
21
22 private static void outerStaticMethod(){
23 System.out.println("I'am outer static method");
24 }
25
26 private static void outerStaticMethod(String param){
27 System.out.println("I'am outer static method with param "+param);
28 }
29}
构造函数,获取类的实例化对象:
1/** * 实例化获取类名对应的类 * * @param clazz 类 * @param constructorArgs 构造函数的各个参数 * @return 实例化对象 */
2public static Object newInstance(Class clazz, Object... constructorArgs) {
3 if (clazz == null) {
4 return null;
5 }
6
7 Object object = null;
8
9 int argLen = constructorArgs == null ? 0 : constructorArgs.length;
10 Class>[] parameterTypes = new Class[argLen];
11 for (int i = 0; i < argLen; i++) {
12 parameterTypes[i] = constructorArgs[i].getClass();
13 }
14
15 try {
16 Constructor constructor = clazz.getDeclaredConstructor(parameterTypes);
17 if (!constructor.isAccessible()) {
18 constructor.setAccessible(true);
19 }
20 object = constructor.newInstance(constructorArgs);
21
22 } catch (Exception e) {
23 e.printStackTrace();
24 }
25
26 return object;
27}
对象方法的反射调用如下:
1/** * 反射调用方法 * * @param object 反射调用的对象实例 * @param methodName 反射调用的对象方法名 * @param methodArgs 反射调用的对象方法的参数列表 * @return 反射调用执行的结果 */
2public static Object invokeMethod(Object object, String methodName, Object... methodArgs) {
3 if (object == null) {
4 return null;
5 }
6
7 Object result = null;
8 Class> clazz = object.getClass();
9 try {
10 Method method = clazz.getDeclaredMethod(methodName, obj2class(methodArgs));
11 if (method != null) {
12 if (!method.isAccessible()) {
13 method.setAccessible(true); //当私有方法时,设置可访问
14 }
15 result = method.invoke(object, methodArgs);
16 }
17 } catch (Exception e) {
18 e.printStackTrace();
19 }
20
21 return result;
22
23}
对象属性值的反射获取方法:
1/** * 反射调用,获取属性值 * * @param object 操作对象 * @param fieldName 对象属性 * @return 属性值 */
2public static Object getField(Object object, String fieldName) {
3 if (object == null) {
4 return null;
5 }
6
7 Object result = null;
8 Class> clazz = object.getClass();
9 try {
10 Field field = clazz.getDeclaredField(fieldName);
11 if (field != null) {
12 if (!field.isAccessible()) {
13 field.setAccessible(true);
14 }
15 result = field.get(object);
16 }
17 } catch (Exception e) {
18 e.printStackTrace();
19 }
20 return result;
21}
类属性的反射设置过程:
1/** * 反射调用,设置属性值 * * @param clazz 操作类 * @param fieldName 属性名 * @param value 属性的新值 * @return 设置是否成功 */
2public static boolean setStaticField(Class clazz, String fieldName, Object value) {
3 if (clazz == null) {
4 return false;
5 }
6
7 Object result = null;
8 try {
9 Field field = clazz.getDeclaredField(fieldName);
10 if (field != null) {
11 if (!field.isAccessible()) {
12 field.setAccessible(true);
13 }
14 field.set(null, value);
15 }
16 } catch (Exception e) {
17 e.printStackTrace();
18 return false;
19 }
20 return true;
21}
六、内部类的反射用法
对于内部类,这里比较复杂,而内部类又分static内部类与非static内部类,两者的反射方式还是有区别的,刚开始在这边折腾了好一阵子,一直反射失败。static内部类与非static内部类的反射调用,根本区别在于构造方法不一样。下面通过代码来告诉如何正确。
6.1 static与非static内部类的反射差异
先定义一个包含两个内部类的类:
1class Outer {
2 /** * 普通内部类 */
3 class Inner {
4 private String innerField = "inner Field";
5
6 private Inner(){
7 System.out.println("I'am Inner construction without args");
8 }
9
10 private Inner(String innerField){
11 this.innerField = innerField;
12 System.out.println("I'am Inner construction with args "+ this.innerField);
13 }
14
15 private void innerMethod(){
16 System.out.println("I'am inner method");
17 }
18 }
19
20 /** * 静态内部类 */
21 static class StaticInner {
22
23 private String innerField = "StaticInner Field";
24 private static String innerStaticField = "StaticInner static Field";
25
26 private StaticInner(){
27 System.out.println("I'am StaticInner construction without args");
28 }
29
30 private StaticInner(String innerField){
31 this.innerField = innerField;
32 System.out.println("I'am StaticInner construction with args "+ this.innerField);
33 }
34
35 private void innerMethod(){
36 System.out.println("I'am StaticInner method");
37 }
38
39 private static void innerStaticMethod(){
40 System.out.println("I'am StaticInner static method");
41 }
42 }
43}
对于上面两个内部类,如果直接实例化内部类,该怎么做,抛开private等权限不够的问题,应该是这样的:
静态内部类:
Outer.StaticInner sInner = new Outer.StaticInner();
非静态内部类:
Outer.Inner inner = new Outer().new Inner();
这种差异,在于内部类的构造方法不一样。我们可以通过下面的方法dumpClass()
来比较。
1/** * 获取类的所有 构造函数,属性,方法 * * @param className 类名 * @return */
2public static String dumpClass(String className) {
3 StringBuffer sb = new StringBuffer();
4 Class> clazz;
5 try {
6 clazz = Class.forName(className);
7 } catch (ClassNotFoundException e) {
8 e.printStackTrace();
9 return "";
10 }
11
12 Constructor>[] cs = clazz.getDeclaredConstructors();
13 sb.append("------ Constructor ------> ").append("\n");
14 for (Constructor> c : cs) {
15 sb.append(c.toString()).append("\n");
16 }
17
18 sb.append("------ Field ------>").append("\n");
19 Field[] fs = clazz.getDeclaredFields();
20 for (Field f : fs) {
21 sb.append(f.toString()).append("\n");
22 ;
23 }
24 sb.append("------ Method ------>").append("\n");
25 Method[] ms = clazz.getDeclaredMethods();
26 for (Method m : ms) {
27 sb.append(m.toString()).append("\n");
28 }
29 return sb.toString();
30}
通过dumpClass()
,对比我们会发现,
static内部类的默认构造函数:
private void com.yuanhh.model.Outer$StaticInner.innerMethod()
非static内部类的默认构造函数:
private com.yuanhh.model.Outer$Inner(com.yuanhh.model.Outer)
,多了一个参数com.yuanhh.model.Outer
,也就是说非static内部类保持了外部类的引用。从属性,我们也会发现多了一个final属性final com.yuanhh.model.Outer com.yuanhh.model.Outer$Inner.this$0
,这正是用于存储外部类的属性值。
正是这差异,导致两者的反射调用过程中构造方法的使用不一样。另外内部类的类名使用采用$符号,来连接外部类与内部类,格式为outer$inner。
6.2 static内部类的 ReflectUtils
类用法
1. 调用类的静态方法(先获取类,再调用)
1Class> clazz = ReflectUtils.getClazz("com.yuanhh.model.Outer$StaticInner"); //获取class
2 ReflectUtils.invokeStaticMethod(clazz, "innerStaticMethod"); //无参方法
3 ReflectUtils.invokeStaticMethod(clazz, "innerStaticMethod","yuanhh"); //有参数方法
2. 调用类的非静态方法(先获取对象,再调用)
1Object obj = ReflectUtils.newInstance("com.yuanhh.model.Outer$StaticInner"); //实例化对象
2 ReflectUtils.invokeMethod(obj, "innerMethod"); //无参方法
3 ReflectUtils.invokeMethod(obj, "innerMethod", "yuanhh"); //有参方法
3. set/get类的静态属性(先获取类,再调用)
1 ReflectUtils.getStaticField(clazz, "innerField"); //get操作
2 ReflectUtils.setStaticField(clazz, "innerField", "new value"); //set操作
4. set/get类的非静态属性(先获取对象,再调用)
1ReflectUtils.getField(obj, "innerField"); //get操作
2 ReflectUtils.setField(obj, "innerField", "new value"); //set操作 ### 2.2 非static内部类的 `ReflectUtils`类用法 非static内部类,不能定义静态方法和静态属性,故操作只有两项:
5. 调用类的非静态方法(先获取对象,再调用)
1// 获取外部类实例,这是static内部类所不需要的,注意点
2 Object outObj = ReflectUtils.newInstance("com.yuanhh.model.Outer");
3 Object obj = ReflectUtils.newInstance("com.yuanhh.model.Outer$Inner", outObj); //实例化对象
4 ReflectUtils.invokeMethod(obj, "innerMethod"); //无参方法
5 ReflectUtils.invokeMethod(obj, "innerMethod", "yuanhh"); //有参方法
6. set/get类的非静态属性(先获取对象,再调用)
1ReflectUtils.getField(obj, "innerField"); //get操作
2 ReflectUtils.setField(obj, "innerField", "new value"); //set操作
七、小结
主要
ReflectUtils
类的用法,只需要按1.1ReflectUtils
类用法的方式使用即可,比如反射调用方法,只需知道类与方法名,即可调用完成invokeMethod(Object object, String methodName)
操作简单。static与非static内部类的区别,在2.2与2.3这两节,对于内部类差异仅仅在于传递参数多了一个$符号,以及非static内部类实例化需要加上外部类的实例。
ReflectUtils
类,以及本文所有涉及的代码,即将打包上传。
source: //yuanfentiank789.github.io/2015/07/18/java-reflection
推荐阅读
关于程序员大白
程序员大白是一群哈工大,东北大学,西湖大学和上海交通大学的硕士博士运营维护的号,大家乐于分享高质量文章,喜欢总结知识,欢迎关注[程序员大白],大家一起学习进步!