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

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

前言


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


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


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


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


public class User {
    private final String name = "Bob";
    private final Student student = new Student();
    public String getName() {
        return name;
    }
    public Student getStudent() {
        return student;
    }
}
    User user = new User();
    Class clz = User.class;
    Field field1 = null;
    try{
        field1=clz.getDeclaredField("name");
        field1.setAccessible(true);
        field1.set(user,"xixi");
        System.out.println(user.getName());
    }catch(NoSuchFieldException e){
        e.printStackTrace();
    }catch(IllegalAccessException e){
        e.printStackTrace();
    }


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


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


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


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


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


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


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


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


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


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


public String getName() {
        return "Bob";
    }


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


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


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


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


System.out.println(user.name)
//经过内联优化
System.out.println("Bob")


所以:


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


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


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


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


反射获取static静态变量


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


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


public class User {
 public static String name;
}
field2 = clz.getDeclaredField("name");
field2.setAccessible(true);
//获取静态变量
Object getname=field2.get(null);
System.out.println("修改前"+getname);
//修改静态变量
field2.set(null, "xixi");
System.out.println("修改后"+User.name);


如上述代码:


  • Field.get(null) 可以获取静态变量。
  • Field.set(null,object) 可以修改静态变量。


怎么提升反射效率


  • 1、缓存重复用到的对象


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


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


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


打印结果:


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


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


  • 2、setAccessible(true)


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


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


  • 3、ReflectASM


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

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


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


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


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


反射原理


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

getDeclaredField方法会走到


public native Field getDeclaredField(String name) throws NoSuchFieldException;


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


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


  • 编译阶段,.java文件会被编译成.class文件,.class文件是一种二进制文件,内容是JVM能够识别的机器码。
  • .class文件里面依次存储着类文件的各种信息,比如:版本号、类的名字、字段的描述和描述符、方法名称和描述、是不是public、类索引、字段表集合,方法集合等等数据。
  • 然后,JVM中的类加载器会读取字节码文件,取出二进制数据,加载到内存中,并且解析.class文件的信息。
  • 类加载器会获取类的二进制字节流,在内存中生成代表这个类的java.lang.Class对象。
  • 最后会开始类的生命周期,比如连接、初始化等等。


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


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


说点无关本文的


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


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


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


以后试一试。


拜拜


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


参考


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

目录
相关文章
|
23天前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
15 0
[Java]反射
|
2月前
|
安全 Java 索引
Java——反射&枚举
本文介绍了Java反射机制及其应用,包括获取Class对象、构造方法、成员变量和成员方法。反射允许在运行时动态操作类和对象,例如创建对象、调用方法和访问字段。文章详细解释了不同方法的使用方式及其注意事项,并展示了如何通过反射获取类的各种信息。此外,还介绍了枚举类型的特点和使用方法,包括枚举的构造方法及其在反射中的特殊处理。
62 9
Java——反射&枚举
|
1月前
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
25 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
1月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
16 0
|
2月前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
2月前
|
存储 安全 Java
扫盲java基础-反射(一)
扫盲java基础-反射(一)
|
2月前
|
Java
扫盲java基础-反射(二)
扫盲java基础-反射(二)
|
4月前
|
安全 Java 测试技术
day26:Java零基础 - 反射
【7月更文挑战第26天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
34 5
|
3月前
|
缓存 安全 Java
【Java 第十篇章】反射
Java 反射技术让程序能在运行时动态获取类信息并操作对象,极大提升了灵活性与扩展性。本文将介绍反射的基本概念、原理及应用,包括如何使用 `Class`、`Field`、`Method` 和 `Constructor` 类进行动态操作。此外,还将探讨反射在动态加载、框架开发与代码测试中的应用场景,并提醒开发者注意性能与安全方面的问题,帮助你更合理地运用这一强大工具。
29 0