前言
大家好,我是 jack xu,今天跟大家介绍核心基础里面的反射,反射这个东西你说它重要也重要,不重要也不重要。重要是当你看一些框架的源码时候,里面会用到反射的代码,你不会是看不懂的。不重要是因为我们平时的工作中绝大多数都是在写业务代码,真正操作类的场景很少。这个跟英语一样,不会不影响你的生活,但是当你往上层高层走的时候,不会会制约你的发展。
应用
我把我在工作中用的场景给大家举下例子,加强一下大家学习的信心。一个是我在写 openapi 时候,做一个切面的拦截,获取请求参数,代码如下
另一个是我在写单测的时候,公有的方法很好测,我直接写对象.方法名即可,由于我测的是私有的方法,你直接点是点不出来的,这时候怎么办,就是反射登场的时候,代码如下
最后一个用途是利用反射破坏单例
定义
什么是反射,动态获取类的内容以及动态调用对象的方法和获取属性的机制,就叫做 JAVA 的反射机制。
- 对于给定的一个类 ( Class ) 对象,可以获得这个类 ( Class ) 对象的所有属性和方法。
- 对于给定的一个对象 ( new XXXClassName<? extends Object> ) ,都能够调用它的任意一个属性和方法。
类对象
首先我们看下一个类对象里面包含了很多的东西,我们可以通过反射拿到这些东西。
获取类对象有四种方式:
Class<User> clazz1 = User.class;
Class<?> clazz2 = Class.forName("com.jackxu.reflect.User");
Class<? extends User> clazz3 = new User().getClass();
//类加载器
Class<?> clazz4 = User.class.getClassLoader().loadClass("com.jackxu.reflect.User");
获取类对象以后,我们就可以获取图上的一些结构信息
// 获取类的修饰符
System.out.println(clazz1.getModifiers());
System.out.println(clazz1.getPackage());
System.out.println(clazz1.getName());
System.out.println(clazz1.getSuperclass());
System.out.println(clazz1.getClassLoader());
System.out.println(clazz1.getSimpleName());
// 获取类似实现的所有的接口
System.out.println(clazz1.getInterfaces());
System.out.println(clazz1.getAnnotations());
字段操作
首先交代两个类,一个是 Person 类,有如下一些信息
public class Person {
public String idCard;
private String userName;
public void fun1() {
}
private void fun2() {
}
}
其次是 User 类,User 类继承了 Person 类
public class User extends Person {
private String name;
public String sex;
public static String address;
public User() {
}
private User(String name, String sex) {
this.name = name;
this.sex = sex;
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
private void jump() {
System.out.println("jump ... ");
}
public static void say(String msg) {
System.out.println("say:" + msg);
}
}
现在开始我们的操作,依然是获取一个类对象
Class<User> clazz1 = User.class;
我们来看这个类对象下面关于字段的方法有四个,我们依次来看下
先看 getFields 和 getDeclaredFields 方法有什么区别
//获取当前类以及父类中公有的字段
Field[] fields = clazz1.getFields();
for (Field f : fields) {
System.out.println(f.getModifiers() + " " + f.getName());
}
System.out.println("--------------------------------------------");
//只能获取当前类中所有的字段
Field[] declaredFields = clazz1.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f.getModifiers() + " " + f.getName());
}
执行一下
大家结合上面的类的代码对比一下,其实区别我已经写在注释上了,getFields 方法获得的是当前类以及父类中所有公有的字段,而 getDeclaredFields 方法获取的只是本类中的所有字段,包括私有的。知道这两个方法区别以后,我们来看下另一个方法 getDeclaredField,这是获取当前类的某个具体字段,如下我们获取的是 name 字段,由于 name 字段是私有的,所以需要把访问权限设为 true,最后把 user 对象的 name 字段的值更改为 jack xu。
Field nameFiled = clazz1.getDeclaredField("name");
nameFiled.setAccessible(true);
nameFiled.set(user, "jack xu");
System.out.println(user.getName());
如果是静态字段,没有具体的对象,那么在 set 方法的第一个参数设为 null 即可。
Field addressFiled = clazz1.getDeclaredField("address");
addressFiled.set(null, "shanghai");
System.out.println(User.address);
方法操作
方法的操作和字段的操作类似,我们同样看下它下面的几个方法
先看下 getMethods 和 getDeclaredMethods 方法
//获取当前类以及父类中所有公有的方法
Method[] methods = clazz1.getMethods();
for (Method m : methods) {
System.out.println(m.getModifiers() + " " + m.getName());
}
System.out.println("--------------------------------------------");
// 获取本类中的所有的方法,包括私有的
Method[] declaredMethods = clazz1.getDeclaredMethods();
for (Method m : declaredMethods) {
System.out.println(m.getModifiers() + " " + m.getName());
}
执行一下,我们发现 getMethods 获取的方法有很多, 这是因为 getMethods 方法是获取获取当前类以及父类中所有公有的方法,而 getDeclaredMethods 是获取本类中所有的方法,包括私有的。
方法的调用也是一样,getDeclaredMethod 方法后面第一个参数是方法名,后面传该方法的参数类型(如果有),如果私有方法的话也需要放开下权限,使用的时候,如果是静态方法 set 第一个参数传 null,非静态方法则传对应的实例对象以及参数。
Method jumpMethod = clazz1.getDeclaredMethod("jump");
// 放开私有方法的调用
jumpMethod.setAccessible(true);
jumpMethod.invoke(user);
// 静态方法调用
Method sayMethod = clazz1.getDeclaredMethod("say", String.class);
sayMethod.invoke(null, "大家好");
执行一下
构造器操作
构造器的操作方法和上面一样,也是大同小异的
// 获取所有的公有的构造器
Constructor<?>[] constructors = clazz1.getConstructors();
for (Constructor c : constructors) {
System.out.println(c.getModifiers() + " " + c.getName());
}
System.out.println("--------------------------------------------");
// 获取所有的构造器
Constructor<?>[] declaredConstructors = clazz1.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println(c.getModifiers() + " " + c.getName());
}
getConstructors 获取的是所有公有的构造器,getDeclaredConstructors 获取的是类中所有的构造器,包括私有的。
当然构造器的应用场景就是创建对象,接下来顺便说下创建对象的几种方式。第一种就是最常见的 new 的方式,第二种是通过类对象的 newInstance 方法,第三种是通过构造器的 newInstance 方法,第四种是类实现实现 Cloneable 接口,调用 clone 方法,第五种是序列化与反序列化。
//1、new一个
User user1 = new User();
//2、类对象的newInstance方法
User user2 = clazz1.newInstance();
//3、构造器的newInstance方法
Constructor<User> declaredConstructor = clazz1.getDeclaredConstructor(String.class, String.class);
declaredConstructor.setAccessible(true);
User user3 = declaredConstructor.newInstance("jack xu", "男");
//4、clone
//5、Serializable
反射的优缺点
最后讲下反射的优缺点。。
优点
- 增加程序的灵活性,避免将固有的逻辑程序写死到代码里
- 代码简洁,可读性强,可提高代码的复用率
缺点
- 相较直接调用在量大的情景下反射性能下降
- 内部暴露和安全隐患
我们来做下实验,通过new的方式和反射的方式分别创建一千万个对象,看看谁的用时少。
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
User user = new User();
}
long end = System.currentTimeMillis();
System.out.println("用new的方式创建总耗时:" + (end - start));
Class<User> clazz = User.class;
for (int i = 0; i < 10000000; i++) {
clazz.newInstance();
}
long end2 = System.currentTimeMillis();
System.out.println("用反射的方式创建总耗时:" + (end2 - end));
运行一下
结果出来了,用反射的方式慢了大概8倍的样子,所以反射的性能低不是随便说说的,是有数据为证的,那具体慢在哪里呢,主要是因为调用了本地的 native 方法以及每次 newInstance 都会做安全检查,比较耗时。
总结
反射到这里就全部结束了,本篇文章介绍了一些常用的方法以及应用场景,希望能够给大家带来帮助,反射是高手玩的东西
作者: Jack_xu