
點擊上方藍字關注我,天天一見,給你力量html

前言
昨天有朋友反映好多反射知識沒說到,因此今天算是補充篇,一塊兒看看反射的進階知識點。java
反射能夠修改final類型成員變量嗎?
final咱們應該都知道,修飾變量的時候表明是一個常量,不可修改。那利用反射能不能達到修改的效果呢?git
咱們先試着修改一個用final修飾的String
變量。github
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
,也就是沒有修改到。web
咱們再修改下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)
能夠修改靜態變量。
怎麼提高反射效率
-
一、緩存重複用到的對象
利用緩存,其實我不說你們也都知道,在平時項目中用到屢次的對象也會進行緩存,誰也不會屢次去建立。
可是,這一點在反射中尤其重要,好比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
三、經過以前反射獲得的class進行實例化
耗時:31
因此看出來,只要咱們合理的運用這些反射方法,好比Class.forName,Constructor,Method,Field
等,儘可能在循環外就緩存好實例,就能提升反射的效率,減小耗時。
-
二、setAccessible(true)
以前咱們說過當遇到私有變量和方法的時候,會用到setAccessible(true)
方法關閉安全檢查。這個安全檢查其實也是耗時的。
因此咱們在反射的過程當中能夠儘可能調用setAccessible(true)
來關閉安全檢查,不管是不是私有的,這樣也能提升反射的效率。
-
三、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/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
感謝你們的閱讀,有一塊兒學習的小夥伴能夠關注下公衆號—
碼上積木
❤️每日一個知識點,創建完總體系架構。
點在看你最好看


本文分享自微信公衆號 - 碼上積木(Lzjimu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。