自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java反射進(jìn)階—聊聊反射的幾個(gè)問題

開發(fā) 后端
昨天有朋友反映好多反射知識(shí)沒說到,所以今天算是補(bǔ)充篇,一起看看反射的進(jìn)階知識(shí)點(diǎn)。

[[376885]]

前言

昨天有朋友反映好多反射知識(shí)沒說到,所以今天算是補(bǔ)充篇,一起看看反射的進(jìn)階知識(shí)點(diǎn)。

反射可以修改final類型成員變量嗎?

final我們應(yīng)該都知道,修飾變量的時(shí)候代表是一個(gè)常量,不可修改。那利用反射能不能達(dá)到修改的效果呢?

我們先試著修改一個(gè)用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.     } 

打印出來的結(jié)果,還是Bob,也就是沒有修改到。

我們再修改下student變量試試:

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

可以看到,對于正常的對象變量即使被final修飾也是可以通過反射進(jìn)行修改的。

這是為什么呢?為什么String不能被修改,而普通的對象變量可以被修改呢?

先說結(jié)論,其實(shí)String值也被修改了,只是我們無法通過這個(gè)對象獲取到修改后的值。

這就涉及到JVM的內(nèi)聯(lián)優(yōu)化了:

內(nèi)聯(lián)函數(shù),編譯器將指定的函數(shù)體插入并取代每一處調(diào)用該函數(shù)的地方(上下文),從而節(jié)省了每次調(diào)用函數(shù)帶來的額外時(shí)間開支。

簡單的說,就是JVM在處理代碼的時(shí)候會(huì)幫我們優(yōu)化代碼邏輯,比如上述的final變量,已知final修飾后不會(huì)被修改,所以獲取這個(gè)變量的時(shí)候就直接幫你在編譯階段就給賦值了。

所以上述的getName方法經(jīng)過JVM編譯內(nèi)聯(lián)優(yōu)化后會(huì)變成:

  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。這是因?yàn)镾ystem.out.println(user.name)這一句在經(jīng)過編譯后,會(huì)被寫成:

  1. System.out.println(user.name
  2.  
  3. //經(jīng)過內(nèi)聯(lián)優(yōu)化 
  4.  
  5. System.out.println("Bob"

所以:

「反射是可以修改final變量的,但是如果是基本數(shù)據(jù)類型或者String類型的時(shí)候,無法通過對象獲取修改后的值,因?yàn)镴VM對其進(jìn)行了內(nèi)聯(lián)優(yōu)化。」

那有沒有辦法獲取修改后的值呢?

有,可以通過反射中的Field.get(Object obj)方法獲?。?/p>

  1. //獲取field對應(yīng)的變量在user對象中的值 
  2. System.out.println("修改后"+field.get(user)); 

反射獲取static靜態(tài)變量

說完了final,再說說static,怎么修改static修飾的變量呢?

我們知道,靜態(tài)變量是在類的實(shí)例化之前就進(jìn)行了初始化(類的初始化階段),所以靜態(tài)變量是跟著類本身走的,跟具體的對象無關(guān),所以我們獲取變量就不需要傳入對象,直接傳入null即可:

 

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

如上述代碼:

  • Field.get(null) 可以獲取靜態(tài)變量。
  • Field.set(null,object) 可以修改靜態(tài)變量。

怎么提升反射效率

1、緩存重復(fù)用到的對象

利用緩存,其實(shí)我不說大家也都知道,在平時(shí)項(xiàng)目中用到多次的對象也會(huì)進(jìn)行緩存,誰也不會(huì)多次去創(chuàng)建。

但是,這一點(diǎn)在反射中尤為重要,比如Class.forName方法,我們做個(gè)測試:

  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,直接實(shí)例化 
  8.         user = new User(); 
  9.         //方法2,每次都通過反射獲取class,然后實(shí)例化 
  10.         user = (User) Class.forName("com.example.studynote.reflection.User").newInstance(); 
  11.         //方法3,通過之前反射得到的class進(jìn)行實(shí)例化 
  12.         user = (User) clz.newInstance(); 
  13.     } 
  14.  
  15.     System.out.println("耗時(shí):" + (System.currentTimeMillis() - startTime)); 

打印結(jié)果:

  1. 1、直接實(shí)例化 
  2. 耗時(shí):15 
  3.  
  4. 2、每次都通過反射獲取class,然后實(shí)例化 
  5. 耗時(shí):671 
  6.  
  7. 3、通過之前反射得到的class進(jìn)行實(shí)例化 
  8. 耗時(shí):31 

所以看出來,只要我們合理的運(yùn)用這些反射方法,比如Class.forName,Constructor,Method,F(xiàn)ield等,盡量在循環(huán)外就緩存好實(shí)例,就能提高反射的效率,減少耗時(shí)。

2、setAccessible(true)

之前我們說過當(dāng)遇到私有變量和方法的時(shí)候,會(huì)用到setAccessible(true)方法關(guān)閉安全檢查。這個(gè)安全檢查其實(shí)也是耗時(shí)的。

所以我們在反射的過程中可以盡量調(diào)用setAccessible(true)來關(guān)閉安全檢查,無論是否是私有的,這樣也能提高反射的效率。

3、ReflectASM

ReflectASM 是一個(gè)非常小的 Java 類庫,通過代碼生成來提供高性能的反射處理,自動(dòng)為 get/set 字段提供訪問類,訪問類使用字節(jié)碼操作而不是 Java 的反射技術(shù),因此非???。

ASM是一個(gè)通用的Java字節(jié)碼操作和分析框架。它可以用于修改現(xiàn)有類或直接以二進(jìn)制形式動(dòng)態(tài)生成類。

簡單的說,這是一個(gè)類似反射,但是不同于反射的高性能庫。他的原理是通過ASM庫,生成了一個(gè)新的類,然后相當(dāng)于直接調(diào)用新的類方法,從而完成反射的功能。

感興趣的可以去看看源碼,實(shí)現(xiàn)原理比較簡單——https://github.com/EsotericSoftware/reflectasm。

「小總結(jié):」經(jīng)過上述三種方法,我想反射也不會(huì)那么可怕到大大影響性能的程度了,如果真的發(fā)現(xiàn)反射影響了性能以及實(shí)際使用的情況,也許可以研究下,是否是因?yàn)闆]用對反射和沒有處理好反射相關(guān)的緩存呢?

反射原理

如果我們試著查看這些反射方法的源碼,會(huì)發(fā)現(xiàn)最終都會(huì)走到native方法中,比如

getDeclaredField方法會(huì)走到

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

那么在底層,是怎么獲取到類的相關(guān)信息的呢?

首先回顧下JVM加載Java文件的過程:

  • 編譯階段,.java文件會(huì)被編譯成.class文件,.class文件是一種二進(jìn)制文件,內(nèi)容是JVM能夠識(shí)別的機(jī)器碼。
  • .class文件里面依次存儲(chǔ)著類文件的各種信息,比如:版本號、類的名字、字段的描述和描述符、方法名稱和描述、是不是public、類索引、字段表集合,方法集合等等數(shù)據(jù)。
  • 然后,JVM中的類加載器會(huì)讀取字節(jié)碼文件,取出二進(jìn)制數(shù)據(jù),加載到內(nèi)存中,并且解析.class文件的信息。
  • 類加載器會(huì)獲取類的二進(jìn)制字節(jié)流,在內(nèi)存中生成代表這個(gè)類的java.lang.Class對象。
  • 最后會(huì)開始類的生命周期,比如連接、初始化等等。

而反射,就是去操作這個(gè) java.lang.Class對象,這個(gè)對象中有整個(gè)類的結(jié)構(gòu),包括屬性方法等等。

總結(jié)來說就是,.class是一種有順序的結(jié)構(gòu)文件,而Class對象就是對這種文件的一種表示,所以我們能從Class對象中獲取關(guān)于類的所有信息,這就是反射的原理。

說點(diǎn)無關(guān)本文的

最近有一些關(guān)于文章中分析源碼部分的想法,以前總想把源碼原封不動(dòng)的搬上來,好讓大家線下也能找到相關(guān)的源碼然后通讀。

但是可能這樣不大現(xiàn)實(shí)?而且也造成了很多朋友讀文章的障礙,很可能當(dāng)時(shí)一知半解,下來全部忘記,至少我就是這樣的哈哈。

所以可能在寫文章中涉及到源碼解析部分,盡量精簡寫出來,或者直接貼上偽代碼能更方便大家理解吧~

以后試一試。

拜拜

Android體系架構(gòu)(連載文章、腦圖、面試專題):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

本文轉(zhuǎn)載自微信公眾號「碼上積木」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼上積木公眾號。

 

責(zé)任編輯:武曉燕 來源: 碼上積木
相關(guān)推薦

2021-01-18 10:33:53

Java反射模塊

2021-10-12 00:04:24

腳本備份MariDB

2013-05-06 15:42:49

2021-03-03 21:24:57

數(shù)據(jù)倉庫工具

2010-06-09 16:57:14

路由選擇協(xié)議

2009-09-07 14:39:14

2011-05-18 11:31:56

數(shù)據(jù)安全數(shù)據(jù)備份

2011-07-01 09:31:49

.net

2024-06-04 00:00:30

C#反射編程

2011-07-04 16:40:39

QT 串口 QML

2024-11-08 08:56:01

2022-01-12 08:53:04

數(shù)字化疫情企業(yè)發(fā)展

2009-11-06 14:07:58

Oracle用戶表空間

2013-08-29 09:47:32

開源hypervisor

2013-11-12 09:16:00

SDN思科Insieme

2012-04-05 13:50:38

Java

2015-09-23 09:08:38

java反射

2021-11-26 07:31:43

Java反射程序

2011-07-22 13:00:46

java

2011-04-01 14:50:56

Java的反射機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號