真的影响!!!
什么是反射
回忆下初学java时的场景:
一切皆对象!new出对象后,直接用对象点方法执行,完成一次调用!(当然也可以用类直接点方法,执行静态方法)
直到学习了jdbc,第一步就是Class.forName(String className).当时老师说,这是为了加载驱动0.0,如果不执行这一句直接获取jdbc连接,会报找不到类(后来的版本改为自动加载,不需要显示的调用Class.forName了)。这里就用到了反射。那么反射到底是什么?
有反就有正,正常情况是:已知一个类,可以通过类名调用静态方法及静态属性,或者new出对象调用实例方法及实例属性。而对于一个未知的类,通过Reflection(反射)动态解析获取类的属性和方法并使用,这就是反射。
反射是java的一个非常重要的高级特性,使用反射可以完全解析程序中的任何类并使用,使程序拥有了动态特性。
举个简单例子,sql查出来的字段如何自动填充到bean中?(此处有个前提,db字段和bean属性有规律,或者存在一个映射关系,且bean实现了属性的get/set方法)
1.sql查出来的结果集转化为map(k-v : 字段名-值)
2.通过反射解析bean获取属性(Field)及方法(Method)
3.通过map key对应到属性及方法,如 user_name,对应bean属性userName,方法setUserName
4.创建一个bean实例A(getInstance),执行方法Method 的invoke方法,传入map value,相当于执行A.setUserName(value)
这样我们就可以处理所有符合上述情况的查询,而不是在程序中写死只能处理一个具体的类,orm框架就是大量用到了反射。
反射为什么影响效率
开局一句话,剩下全靠编!
反射这么好用,真的影响效率吗,还能愉快使用吗?
使用反射真的没有直接调用快,但是该用还是要用,影响效率的原因搞清楚了,尽可能避免就是了。下面来看看,反射怎么影响效率了 。
测试反射是否影响效率,思路如下:
1.创建一个Student类,属性包含姓名、年龄、性别,方法say返回属性拼接
2.创建一个数据初始化方法,可以返回指定数量的Student集合,Student名字固定,年龄性别可以给随机值
3.创建正常遍历Student集合的方法test
4.创建反射遍历Student集合的方法testReflect,此方法在遍历开始前反射得到Student say方法的Method
5.创建反射遍历Student集合的方法testReflectOne,此方法在遍历开始后每次遍历时反射得到Student say方法的Method
6.创建反射遍历Student集合的方法testReflectPre,此方法提前反射获取Student say方法的Method,多次遍历不会重复使用反射
7.循环多次调用3/4/5/6方法,打印平均及总耗时,对比效率
代码如下:
import lombok.Data;
/**
* <p></p>
*
* @author 0.0
* @since 2020-09-27 16:52
*/
@Data
public class Student {
private String name;
private int age;
private int sex;// 男/女/位置:0/1/-1
public String say() {
StringBuffer sb = new StringBuffer();
sb.append("我的名字叫[").append(name).append("], 年龄[").append(age).append("], 性别[").append(this.getSexDisplay()).append("]");
return sb.toString();
}
private String getSexDisplay() {
switch (sex) {
case 0: return "男";
case 1: return "女";
case -1: return "未知";
default: return "未知";
}
}
}
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* <p></p>
*
* @author 0.0
* @since 2020-09-27 16:52
*/
public class MainTest {
private static final Random RANDOM = new Random();
// 初始化数据的总数
private static final int DATA_INIT_TOTAL = 4000000;
private static final List<Long> COST = new ArrayList<>();
private static final List<Long> REFLECT_COST = new ArrayList<>();
private static final List<Long> REFLECT_PRE_COST = new ArrayList<>();
private static final List<Long> REFLECT_MORE_COST = new ArrayList<>();
public static void main(String[] args) throws Exception {
// 执行次数
int total = 15;
Method sayMethod = Student.class.getDeclaredMethod("say");
List<Student> studentList = initData(DATA_INIT_TOTAL);
for (int i = 0; i < total; i++) {
test(i, studentList);
testReflectPre(i, sayMethod, studentList);
testReflect(i, studentList);
testReflectOne(i, studentList);
System.out.println("-----");
}
System.out.println("平均直接调用耗时:" + COST.stream().mapToLong(Long::longValue).sum() / COST.size() + "ms");
System.out.println("平均预反射调用耗时:" + REFLECT_PRE_COST.stream().mapToLong(Long::longValue).sum() / COST.size() + "ms");
System.out.println("平均反射(一次)调用耗时:" + REFLECT_COST.stream().mapToLong(Long::longValue).sum() / COST.size() + "ms");
System.out.println("平均反射(每次)调用耗时:" + REFLECT_MORE_COST.stream().mapToLong(Long::longValue).sum() / COST.size() + "ms");
}
/**
* 直接调用方法
* @param total
* @throws Exception
*/
private static void test(int total, List<Student> studentList) throws Exception {
long beginProcessTime = beginTime();
for (Student student : studentList) {
student.say();
}
long costTime = costTime(beginProcessTime);
COST.add(costTime);
System.out.println("第[" + total + "]直接处理总耗时:" + costTime + "ms");
}
/**
* 只获取一次反射Method
* @param total
* @throws Exception
*/
private static void testReflect(int total, List<Student> studentList) throws Exception {
long beginProcessTime = beginTime();
Method sayMethod = Student.class.getDeclaredMethod("say");
for (Student student : studentList) {
sayMethod.invoke(student, null).toString();
}
long costTime = costTime(beginProcessTime);
REFLECT_COST.add(costTime);
System.out.println("第[" + total + "]反射(一次)处理总耗时:" + costTime + "ms");
}
/**
* 每次执行时反射获取Method
* @param total
* @throws Exception
*/
private static void testReflectOne(int total, List<Student> studentList) throws Exception {
long beginProcessTime = beginTime();
for (Student student : studentList) {
Method sayMethod = Student.class.getDeclaredMethod("say");
sayMethod.invoke(student, null).toString();
}
long costTime = costTime(beginProcessTime);
REFLECT_MORE_COST.add(costTime);
System.out.println("第[" + total + "]反射(每次)处理总耗时:" + costTime + "ms");
}
/**
* 预先获取反射方法,此处只执行invoke
* @param total
* @param sayMethod
* @throws Exception
*/
private static void testReflectPre(int total, Method sayMethod, List<Student> studentList) throws Exception {
long beginProcessTime = beginTime();
int i = 0;
//double totalCost = 0d;
for (Student student : studentList) {
i++;
long startTime = beginTime();
sayMethod.invoke(student, null).toString();
long totalTime = costTime(startTime);
//totalCost += totalTime;
/*if (i % 10000 == 0) {
System.out.println("totalCost:" + totalCost + "ms");
System.out.println("totalCost avg:" + String.format("%.5f", totalCost / i) + "ms");
}*/
}
long costTime = costTime(beginProcessTime);
REFLECT_PRE_COST.add(costTime);
System.out.println("第[" + total + "]预反射处理总耗时:" + costTime + "ms");
}
private static long beginTime() {
long beginTime = System.currentTimeMillis();
return beginTime;
}
private static long costTime(long beginTime) {
long endTime = System.currentTimeMillis();
return (endTime - beginTime);
}
public static List<Student> initData(int size) {
List<Student> studentList = new ArrayList<>();
for (int i = 0; i < size; i++) {
Student tmp = new Student();
tmp.setName("张三");
tmp.setAge(RANDOM.nextInt(100));
tmp.setSex((i + RANDOM.nextInt(5)) % 2);
studentList.add(tmp);
}
return studentList;
}
}
看下执行结果:
第[0]直接处理总耗时:587ms
第[0]预反射处理总耗时:502ms
第[0]反射(一次)处理总耗时:377ms
第[0]反射(每次)处理总耗时:780ms
-----
第[1]直接处理总耗时:292ms
第[1]预反射处理总耗时:373ms
第[1]反射(一次)处理总耗时:280ms
第[1]反射(每次)处理总耗时:686ms
-----
第[2]直接处理总耗时:274ms
第[2]预反射处理总耗时:294ms
第[2]反射(一次)处理总耗时:279ms
第[2]反射(每次)处理总耗时:647ms
-----
第[3]直接处理总耗时:273ms
第[3]预反射处理总耗时:295ms
第[3]反射(一次)处理总耗时:284ms
第[3]反射(每次)处理总耗时:645ms
-----
第[4]直接处理总耗时:269ms
第[4]预反射处理总耗时:302ms
第[4]反射(一次)处理总耗时:278ms
第[4]反射(每次)处理总耗时:644ms
-----
第[5]直接处理总耗时:276ms
第[5]预反射处理总耗时:296ms
第[5]反射(一次)处理总耗时:280ms
第[5]反射(每次)处理总耗时:647ms
-----
...
再来一次
第[0]直接处理总耗时:600ms
第[0]预反射处理总耗时:480ms
第[0]反射(一次)处理总耗时:376ms
第[0]反射(每次)处理总耗时:795ms
-----
第[1]直接处理总耗时:283ms
第[1]预反射处理总耗时:374ms
第[1]反射(一次)处理总耗时:282ms
第[1]反射(每次)处理总耗时:704ms
-----
第[2]直接处理总耗时:274ms
第[2]预反射处理总耗时:299ms
第[2]反射(一次)处理总耗时:284ms
第[2]反射(每次)处理总耗时:660ms
-----
第[3]直接处理总耗时:277ms
第[3]预反射处理总耗时:298ms
第[3]反射(一次)处理总耗时:285ms
第[3]反射(每次)处理总耗时:658ms
-----
第[4]直接处理总耗时:270ms
第[4]预反射处理总耗时:303ms
第[4]反射(一次)处理总耗时:285ms
第[4]反射(每次)处理总耗时:656ms
-----
...
400W对象的遍历,能说明一定问题,反射后只是使用method.invoke方法,执行效率和直接调用差不多!,频繁使用getDeclaredMethod方法会极大地影响程序执行效率!
进入getDeclaredMethod方法,可以看到,先检查,然后查找
检查啥:“Check if client is allowed to access members. If access is denied”,方法是否允许访问。修饰符,包等
查找啥:遍历类的所有方法,根据名字和入参匹配唯一一个返回
@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
所以 这是一个比较复杂的操作,执行的多了,必然耗时。
但是,一个类的属性和方法是有限的,没必要每次都反射获取吧?所以,如果可能,提前反射好,后面直接用,效率影响不大