文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java反射进阶—聊聊反射的几个问题

2024-12-03 12:46

关注

前言

昨天有朋友反映好多反射知识没说到,所以今天算是补充篇,一起看看反射的进阶知识点。

反射可以修改final类型成员变量吗?

final我们应该都知道,修饰变量的时候代表是一个常量,不可修改。那利用反射能不能达到修改的效果呢?

我们先试着修改一个用final修饰的String变量。

  1. public class User { 
  2.     private final String name = "Bob"
  3.     private final Student student = new Student(); 
  4.      
  5.     public String getName() { 
  6.         return name
  7.     } 
  8.  
  9.     public Student getStudent() { 
  10.         return student; 
  11.     } 
  12.  
  13.  
  14.     User user = new User(); 
  15.     Class clz = User.class; 
  16.     Field field1 = null
  17.     try{ 
  18.         field1=clz.getDeclaredField("name"); 
  19.         field1.setAccessible(true); 
  20.         field1.set(user,"xixi"); 
  21.         System.out.println(user.getName()); 
  22.     }catch(NoSuchFieldException e){ 
  23.         e.printStackTrace(); 
  24.     }catch(IllegalAccessException e){ 
  25.         e.printStackTrace(); 
  26.     } 

打印出来的结果,还是Bob,也就是没有修改到。

我们再修改下student变量试试:

  1. field1 = clz.getDeclaredField("student"); 
  2. field1.setAccessible(true); 
  3. field1.set(user, new Student()); 
  4.  
  5. 打印: 
  6. 修改前com.example.studynote.reflection.Student@77459877 
  7. 修改后com.example.studynote.reflection.Student@72ea2f77 

可以看到,对于正常的对象变量即使被final修饰也是可以通过反射进行修改的。

这是为什么呢?为什么String不能被修改,而普通的对象变量可以被修改呢?

先说结论,其实String值也被修改了,只是我们无法通过这个对象获取到修改后的值。

这就涉及到JVM的内联优化了:

内联函数,编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。

简单的说,就是JVM在处理代码的时候会帮我们优化代码逻辑,比如上述的final变量,已知final修饰后不会被修改,所以获取这个变量的时候就直接帮你在编译阶段就给赋值了。

所以上述的getName方法经过JVM编译内联优化后会变成:

  1. public String getName() { 
  2.        return "Bob"
  3.    } 

所以无论怎么修改,都获取不到修改后的值。

有的朋友可能提出直接获取name呢?比如这样:

  1. //修改为public 
  2. public final String name = "Bob"
  3.  
  4. //反射修改后,打印user.name 
  5. field1=clz.getDeclaredField("name"); 
  6. field1.setAccessible(true); 
  7. field1.set(user,"xixi"); 
  8. System.out.println(user.name); 

不好意思,还是打印出来Bob。这是因为System.out.println(user.name)这一句在经过编译后,会被写成:

  1. System.out.println(user.name
  2.  
  3. //经过内联优化 
  4.  
  5. System.out.println("Bob"

所以:

「反射是可以修改final变量的,但是如果是基本数据类型或者String类型的时候,无法通过对象获取修改后的值,因为JVM对其进行了内联优化。」

那有没有办法获取修改后的值呢?

有,可以通过反射中的Field.get(Object obj)方法获取:

  1. //获取field对应的变量在user对象中的值 
  2. System.out.println("修改后"+field.get(user)); 

反射获取static静态变量

说完了final,再说说static,怎么修改static修饰的变量呢?

我们知道,静态变量是在类的实例化之前就进行了初始化(类的初始化阶段),所以静态变量是跟着类本身走的,跟具体的对象无关,所以我们获取变量就不需要传入对象,直接传入null即可:

 

  1. public class User { 
  2.  public static String name
  3.  
  4.  
  5. field2 = clz.getDeclaredField("name"); 
  6. field2.setAccessible(true); 
  7. //获取静态变量 
  8. Object getname=field2.get(null); 
  9. System.out.println("修改前"+getname); 
  10.  
  11. //修改静态变量 
  12. field2.set(null"xixi"); 
  13. System.out.println("修改后"+User.name); 

如上述代码:

怎么提升反射效率

1、缓存重复用到的对象

利用缓存,其实我不说大家也都知道,在平时项目中用到多次的对象也会进行缓存,谁也不会多次去创建。

但是,这一点在反射中尤为重要,比如Class.forName方法,我们做个测试:

  1. long startTime = System.currentTimeMillis(); 
  2.     Class clz = Class.forName("com.example.studynote.reflection.User"); 
  3.     User user
  4.     int i = 0; 
  5.     while (i < 1000000) { 
  6.         i++; 
  7.         //方法1,直接实例化 
  8.         user = new User(); 
  9.         //方法2,每次都通过反射获取class,然后实例化 
  10.         user = (User) Class.forName("com.example.studynote.reflection.User").newInstance(); 
  11.         //方法3,通过之前反射得到的class进行实例化 
  12.         user = (User) clz.newInstance(); 
  13.     } 
  14.  
  15.     System.out.println("耗时:" + (System.currentTimeMillis() - startTime)); 

打印结果:

  1. 1、直接实例化 
  2. 耗时:15 
  3.  
  4. 2、每次都通过反射获取class,然后实例化 
  5. 耗时:671 
  6.  
  7. 3、通过之前反射得到的class进行实例化 
  8. 耗时:31 

所以看出来,只要我们合理的运用这些反射方法,比如Class.forName,Constructor,Method,Field等,尽量在循环外就缓存好实例,就能提高反射的效率,减少耗时。

2、setAccessible(true)

之前我们说过当遇到私有变量和方法的时候,会用到setAccessible(true)方法关闭安全检查。这个安全检查其实也是耗时的。

所以我们在反射的过程中可以尽量调用setAccessible(true)来关闭安全检查,无论是否是私有的,这样也能提高反射的效率。

ReflectASM

ReflectASM 是一个非常小的 Java 类库,通过代码生成来提供高性能的反射处理,自动为 get/set 字段提供访问类,访问类使用字节码操作而不是 Java 的反射技术,因此非常快。

ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。

简单的说,这是一个类似反射,但是不同于反射的高性能库。他的原理是通过ASM库,生成了一个新的类,然后相当于直接调用新的类方法,从而完成反射的功能。

感兴趣的可以去看看源码,实现原理比较简单——https://github.com/EsotericSoftware/reflectasm。

「小总结:」经过上述三种方法,我想反射也不会那么可怕到大大影响性能的程度了,如果真的发现反射影响了性能以及实际使用的情况,也许可以研究下,是否是因为没用对反射和没有处理好反射相关的缓存呢?

反射原理

如果我们试着查看这些反射方法的源码,会发现最终都会走到native方法中,比如

getDeclaredField方法会走到

  1. public native Field getDeclaredField(String name) throws NoSuchFieldException; 

那么在底层,是怎么获取到类的相关信息的呢?

首先回顾下JVM加载Java文件的过程:

而反射,就是去操作这个 java.lang.Class对象,这个对象中有整个类的结构,包括属性方法等等。

总结来说就是,.class是一种有顺序的结构文件,而Class对象就是对这种文件的一种表示,所以我们能从Class对象中获取关于类的所有信息,这就是反射的原理。

说点无关本文的

最近有一些关于文章中分析源码部分的想法,以前总想把源码原封不动的搬上来,好让大家线下也能找到相关的源码然后通读。

但是可能这样不大现实?而且也造成了很多朋友读文章的障碍,很可能当时一知半解,下来全部忘记,至少我就是这样的哈哈。

所以可能在写文章中涉及到源码解析部分,尽量精简写出来,或者直接贴上伪代码能更方便大家理解吧~

以后试一试。

拜拜

Android体系架构(连载文章、脑图、面试专题):https://github.com/JiMuzz/Android-Architecture

参考

https://juejin.cn/post/6844903905483030536 https://www.zhihu.com/question/46883050 https://juejin.cn/post/6917984253360177159 https://blog.csdn.net/PiaoMiaoXiaodao/article/details/79871313 https://www.cnblogs.com/coding-night/p/10772631.html

本文转载自微信公众号「码上积木」,可以通过以下二维码关注。转载本文请联系码上积木公众号。

 

来源: 码上积木内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯