反射的学习使用
日常的学习工作中,可能用到反射的地方不太多,但看看一些优秀框架的源码,会发现基本上都离不开反射的使用;因此本篇博文将专注下如何使用反射
本片博文布局如下:
- 反射是什么,有什么用,可以做什么
- 如何使用反射
实例:
- 利用反射方式,获取一个类的所有成员变量的name及值
- 通过反射方式,修改对象的私有成员变量
- 会通过写一个BeanUtils实现对象的成员变量值拷贝来覆盖上面两个场景
I. 反射定义
指程序可以访问、检测和修改它本身状态或行为的一种能力
直接说定义的话,可能并不能非常清晰的解释说明,结合作用进行描述
反射可以干什么?
1 | 在运行时构造任意一个类的对象。 |
有了上面四点,基本上你想干嘛就可以干嘛,比如我现在就有下面这个类
1 | public class RefectTest extends MyRefect implements IRefect { |
现在我有了clz,其赋值语句为 Class clz = RefectTest.class
, 那么我可以干啥?
创建一个 RefectTest 对象
1
2
3
4
5
6// 若有默认构造方法
RefectTest instance = clz.newIntance();
// 若需要传参数
Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class);
RefectTest instance2 = con.newInstance(10, true, new RefectTest());判断父类是否是 MyRefect
1
2// 判断MyRefect是否为clz的父类
boolean ans = MyRefect.class.isAssignableFrom(clz);获取所有的成员变量
1
2// 获取所有的成员变量(包括私有的)
Field[] fields = clz.getDeclaredFields();获取所有的方法
1
2// 获取所有的成员方法(包括私有方法)
Method[] methods = clz.getDeclaredMethods();
上面给出了可以干些什么,并给了对应的简单示例,引入了几个新的类Constructor
, Field
, Method
, 下面将详细解释这三个类是什么,怎么用
II. 反射的使用
努力结合实际的应用场景,给出每种利用反射的实现对应需求的使用姿势,有些场景可能并不是特别贴切,欢迎提出给合适的场景以此进行替换
1. 通过反射创建对象
这是个比较常见的场景,我在使用了自定义注解时,通常会这么晚
应用场景:
我定义了一个校验器的注解ValDot
,注解中有个校验规则class对象,如下
1 | public interface ICheckRule { |
上面定义了注解和校验条件,接着进入整体,在切面中,需要获取
1 | @Aspect |
上面是一个较好的利用反射获取实例的应用场景,想一想,如果不用反射,这个校验规则怎么传进来呢,这个时候就没那么方便了(当然也不是不可以,最简单的就是拿一个Holder持有类名到类对象的映射关系,然后在注解中传类名,也可以达到上面的效果)
还有一种场景可能就比较蛋疼了,如果一个类没有默认构造方法,通过反射就没法直接用class.newInstanace()
了
Constructor
构造器类
根据Class优先获取到 Constructor
对象,然后传入需要的构造参数, 测试如下
1 | public class ConTest { |
输出
1 | ConTest{a=10, b=20} |
一般常用下面四种方式获取
1 | // 根据参数类型获取匹配的构造器 |
2. 判断class的继承关系
判断是否为基础数据类型
基本类型较为特殊,所以JDK很人性化的给封装了一个方法,Class#isPrimitive
因此返回true的类型有:
- int
- long
- short
- byte
- char
- boolean
封装后的类型,返回的依然是false
附带一句,是没有null.class
这种用法的
判断是否为另一个类的子类,另一个接口的实现类
通常我们利用 instanceof
关键字来判断继承关系,但是这个是针对对象来的,现在给一个class,要怎么玩?
看下面,主要就是 Class#isAssignableFrom()
的功劳了
1 | public class ExtendTest { |
需要注意一点,父类作为调用方,子类作为参数
结合泛型时,获取泛型的实际类型
泛型,又是一个有意思的功能,这里不多说,继承一个泛型基类,然后问题是如何通过反射获得泛型签名中的类型,一般会在继承或实现泛型接口时会用到它。
1 | class A<T, ID> { |
换成泛型接口呢 ?
1 | interface A<T, ID> { |
3. 获取成员变量
获取成员变量,主要是根据 B.class.getDeclaredFields()
来获取所有声明的变量,这个应用场景会和下面的获取方法并执行联合一起说明
1 | // 获取指定的公共成员变量 |
这个主要返回 Field
对象,现在有了Field,可以做些啥?
判断成员的修饰
Field#getModifiers()
1
2
3
4
5
6
7
8int modify = field.getModifiers();
// 是否是静态变量
boolean ans = Modifier.isStatic(modifier);
// 是否是公共变量
boolean ans = Modifier.isPublic(modifier);
// 是否不可变
boolean ans = Modifier.isFinal(modifier);
// ...获取成员的变量名 :
field#getName()
获取成员对应的value:
field#get(instance)
- 对于静态成员,instance可以为null
- 对于非静态成员,instance必须为一个实例对象
获取注解:
field#getAnnotations()
- 这个就厉害了,hibernate的校验框架,在成员变量上加一个注解
Max
,就可以设置参数的最大值,其实就是通过反射获取到注解,然后进行相应的逻辑
- 这个就厉害了,hibernate的校验框架,在成员变量上加一个注解
4. 获取方法
获取方法,同上面的差不多,也有四种方式
1 | // 根据方法名,参数类型获取公共方法 |
返回了一个Method
类,那么这个东西又有一些什么功能?
获取方法名
Method#getName()
获取方法所在的类 :
Method#getDeclaringClass()
获取方法返回类型 :
Method#getReturnType()
获取方法上的注解 :
Method#getAnnotations()
执行方法 有了这个就可以做很多事情了,实例中给出说明
1
2
3
4// 设置方法可访问(即私有方法也可以被调用)
method.setAccessible(true);
// instance为实例对象, args为传入参数
method.invoke(instance, args)
III. 实例DEMO
通过反射的方式,实现一个 BeanUtils
,实现Bean的拷贝
当一个Bean有较多的成员变量时,如果我们采用最原始的setXXX()
来一次赋值的时候,一是实现比较繁琐,其次就是当Bean的字段发生变动之后,也需要同步的修改,那么我们借助反射的方式,实现一个优雅的 BeanUtils
工具类
1 | public class BeanUtils { |
IV. 小结
反射的四种用途
创建一个 RefectTest 对象
1
2
3
4
5
6// 若有默认构造方法
RefectTest instance = clz.newIntance();
// 若需要传参数
Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class);
RefectTest instance2 = con.newInstance(10, true, new RefectTest());判断父类是否是 MyRefect
1
2// 判断MyRefect是否为clz的父类
boolean ans = MyRefect.class.isAssignableFrom(clz);获取所有的成员变量
1
2// 获取所有的成员变量(包括私有的)
Field[] fields = clz.getDeclaredFields();获取所有的方法
1
2// 获取所有的成员方法(包括私有方法)
Method[] methods = clz.getDeclaredMethods();
使用注意事项
- 操作私有变量,私有方法时,先设置
field.setAccessible(true);
确保可访问 - 反射会带来额外的性能开销
- 可以用
Class#isAssignableFrom()
来判断类继承关系 - 可以用
Class#isPrimitive()
判断是否为基本数据类型 - 可以用
Class#getGenericSuperclass()
获取泛型类型
V. 其他
声明
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正