前言

在我们平时的工作或者面试中,都会经常遇到“反射”这个知识点,通过“反射”我们可以动态的获取到对象的信息以及灵活的调用对象方法等,但是在使用的同时又伴随着另一种声音的出现,那就是“反射”很慢,要少用。难道反射真的很慢?那跟我们平时正常创建对象调用方法比慢多少? 估计很多人都没去测试过,只是”道听途说“。下面我们就直接通过一些测试用例来直观的感受一下”反射“。

正文

准备测试对象

下面先定义一个测试的类TestUser,只有idname属性,以及它们的getter/setter方法,另外还有一个自定义的sayHi方法。

public class TestUser {
private Integer id;
private String name; public String sayHi(){
return "hi";
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

测试创建100万个对象
// 通过普通方式创建TestUser对象
@Test
public void testCommon(){
long start = System.currentTimeMillis();
TestUser user = null;
int i = 0;
while(i<1000000){
++i;
user = new TestUser();
}
long end = System.currentTimeMillis();
System.out.println("普通对象创建耗时:"+(end - start ) + "ms");
} //普通对象创建耗时:10ms // 通过反射方式创建TestUser对象
@Test
public void testReflexNoCache() throws Exception {
long start = System.currentTimeMillis();
TestUser user = null;
int i = 0;
while(i<1000000){
++i;
user = (TestUser) Class.forName("ReflexDemo.TestUser").newInstance();
}
long end = System.currentTimeMillis();
System.out.println("无缓存反射创建对象耗时:"+(end - start ) + "ms");
} //无缓存反射创建对象耗时:926ms

在上面这两个测试方法中,笔者各自测了5次,把他们消耗的时间取了一个平均值,在输出结果中可以看到一个是10ms,一个是926ms,在创建100W个对象的情况下,反射居然慢了90倍左右。wtf?差距居然这么大?难道反射真的这么慢?下面笔者换一种反射的姿势,继续测试一下,看看结果如何?

// 通过缓存反射方式创建TestUser对象
@Test
public void testReflexWithCache() throws Exception {
long start = System.currentTimeMillis();
TestUser user = null;
Class rUserClass = Class.forName("RefleDemo.TestUser");
int i = 0;
while(i<1000000){
++i;
user = (TestUser) rUserClass.newInstance();
}
long end = System.currentTimeMillis();
System.out.println("通过缓存反射创建对象耗时:"+(end - start ) + "ms");
} //通过缓存反射创建对象耗时:41ms

咦?这种操作只需要41ms了,大大提高了反射创建对象的效率。为什么会快这么多呢?

其实通过代码我们可以发现,是Class.forName这个方法比较耗时,它实际上调用了一个本地方法,通过这个方法来要求JVM查找并加载指定的类。所以我们在项目中使用的时候,可以把Class.forName返回的Class对象缓存起来,下一次使用的时候直接从缓存里面获取,这样就极大的提高了获取Class的效率。同理,在我们获取Constructor、Method等对象的时候也可以缓存起来使用,避免每次使用时再来耗费时间创建。

测试反射调用方法
@Test
public void testReflexMethod() throws Exception {
long start = System.currentTimeMillis();
Class testUserClass = Class.forName("RefleDemo.TestUser");
TestUser testUser = (TestUser) testUserClass.newInstance();
Method method = testUserClass.getMethod("sayHi");
int i = 0;
while(i<100000000){
++i;
method.invoke(testUser);
}
long end = System.currentTimeMillis();
System.out.println("反射调用方法耗时:"+(end - start ) + "ms");
} //反射调用方法耗时:330ms

@Test
public void testReflexMethod() throws Exception {
long start = System.currentTimeMillis();
Class testUserClass = Class.forName("RefleDemo.TestUser");
TestUser testUser = (TestUser) testUserClass.newInstance();
Method method = testUserClass.getMethod("sayHi");
int i = 0;
while(i<100000000){
++i;
method.setAccessible(true);
method.invoke(testUser);
}
long end = System.currentTimeMillis();
System.out.println("setAccessible=true 反射调用方法耗时:"+(end - start ) + "ms");
} //setAccessible=true 反射调用方法耗时:188ms

这里我们反射调用sayHi方法1亿次,在调用了method.setAccessible(true)后,发现快了将近一半。查看API可以了解到,jdk在设置获取字段,调用方法的时候会执行安全访问检查,而此类操作会比较耗时,所以通过setAccessible(true)的方式可以关闭安全检查,从而提升反射效率。

极致的反射

除了上面的手段,还有没有什么办法可以更极致的使用反射呢?这里介绍一个高性能反射工具包ReflectASM。它是通过字节码生成的方式来实现的反射机制,下面是一个跟java反射的性能比较。

这里就不介绍它的用法了,有兴趣的朋友可以直接传送过去:https://github.com/EsotericSoftware/reflectasm

结语

最后总结一下,为了更好的使用反射,我们应该在项目启动的时候将反射所需要的相关配置及数据加载进内存中,在运行阶段都从缓存中取这些元数据进行反射操作。大家也不用惧怕反射,虚拟机在不断的优化,只要我们方法用的对,它并没有”传闻“中的那么慢,当我们对性能有极致追求的时候,可以考虑通过三方包,直接对字节码进行操作。

---------------------------------------------------------

公众号博文同步Github仓库,有兴趣的朋友可以帮忙给个Star哦,码字不易,感谢支持。

https://github.com/PeppaLittlePig/blog-wechat

推荐阅读

Java日志正确使用姿势

Java异常处理最佳实践及陷阱防范

论JVM爆炸的几种姿势及自救方法

看完本文有收获?请转发分享给朋友吧

关注「深夜里的程序猿」,分享最干的干货

如何提高使用Java反射的效率?的更多相关文章

  1. 【java提高】---java反射机制

    java反射机制 一.概述 1.什么是反射机制 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态 ...

  2. java反射究竟消耗多少效率

    原文出处 一直以来都对Java反射究竟消耗了多少效率很感兴趣,今晚总算有空进行了一下测试 测试被调用的类和方法 package com.spring.scran; public class TestM ...

  3. 提高java反射速度的方法method.setAccessible(true)

    转载:http://huoyanyanyi10.iteye.com/blog/1317614 提高java反射速度的方法method.setAccessible(true) package com.c ...

  4. Java——反射三种方式的效率对比

    转载自:https://blog.csdn.net/aitcax/article/details/52694423 1 使用field(效率最高)             long start = S ...

  5. Android简易注解View(java反射实现)

    一.引言 Android中通过findViewById在布局文件中找到需要的View,加入一个Activity里面有许多的View需要初始化,那将是一件很繁琐的事情.当然Google一下你会发现有很多 ...

  6. Java 反射 调用私有域和方法(setAccessible)

    Java 反射 调用私有域和方法(setAccessible) @author ixenos AccessibleObject类 Method.Field和Constructor类共同继承了Acces ...

  7. Java 反射在实际开发中的应用

    运行时类型识别(RTTI, Run-Time Type Information)是Java中非常有用的机制,在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型:还有一 ...

  8. 五、JAVA反射、线程

    第五节:Java反射.线程 线程   1.进程:进程是程序的基本执行实体,进程是线程的容器.    线程:被称为轻量进程,是程序执行流的最小单元.线程是进程中的一个实                  ...

  9. [转]Java 反射在实际开发中的应用

    一:Java类加载和初始化 1.1 类加载器(类加载的工具) 1.2 Java使用一个类所需的准备工作 二:Java中RTTI 2.1 :为什么要用到运行时类型信息(就是RTTI) 2.2  :RTT ...

随机推荐

  1. Python函数式实现单例特性

    传统的单例一般是基于类的特性实现,Python模块是天生的单例,下面来个简单的借助模块和函数实现单例特性: gdb = None def get_gdb(): global gdb if gdb is ...

  2. Redis实现简单消息队列

    http://www.jianshu.com/p/9c04890615ba 任务异步化 打开浏览器,输入地址,按下回车,打开了页面.于是一个HTTP请求(request)就由客户端发送到服务器,服务器 ...

  3. R贡献文件中文

    贡献文件 注意: 贡献文件的CRAN区域被冻结,不再被主动维护. 英文 --- 其他语言 手册,教程等由R用户提供.R核心团队对内容不承担任何责任,但我们非常感谢您的努力,并鼓励大家为此列表做出贡献! ...

  4. fastjson 的使用总结

    前言 最近在开发过程中使用了大量的json作为前后端数据交换的方式,由于之前没有对json做过系统的学习,所有在使用过程中查阅了大量的文档与资料,这里主要记录了我在开发后对json以及fastjson ...

  5. 不使用JavaScript实现菜单的打开和关闭

    我在写有菜单栏的网页时,基本都会用响应式设计来适配移动端,例如把不重要的菜单选项隐藏,或者创建一个菜单按钮来控制的菜单的打开和关闭之类的.而我之前一直是使用JavaScript来实现菜单的打开和关闭的 ...

  6. 微信小程序 bug及解决方案

    1.小程序遮罩滚动穿透问题 解决方案: <view class="mask" wx:if="{{showVipRights}}" catchtap='hi ...

  7. c语言之单链表的创建及排序

    今天对之前学习过的链表知识进行简单的总结顺便写点代码:创建一个链表有头插法跟尾插法两种,在下面代码中我们为结点分配的内存实在堆上分配的,因此需要我们手动释放,释放用free()函数 下面代码贴出具体代 ...

  8. Oracle-10:分析函数

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 分析函数: 分析函数,可以修改排序的规则!!!!!!!!!!!!!!!!!!!!!!!! 首先把数据库脚本给放 ...

  9. Python Selenium 之生成Beautiful可视化报告

    提到自动化测试,少不了自动化生成测试报告,更少不了漂亮的测试报告呀!刚好看到在github上有个大神分享了BeautifulReport,与unittest测试框架完美的结合起来,就能生成Beauti ...

  10. golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL

        先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和Map完成路由映射,数组的大小为8,下标为0的代表Get方法,以此类推,而数组的值则是Map,键为URL,值则是我们编写对应的接口.但 ...