Java 反射:框架設計的靈魂
- 2020 年 3 月 2 日
- 筆記
點擊上方「碼農沉思錄」,選擇「設為星標」
優質文章,及時送達
在學習 Java 反射之前,先讓我們看看這幾個概念。
解釋型語言和編譯型語言
解釋型語言:不需要編譯,在運行的時候逐行翻譯解釋;修改程式碼時可以直接修改,可以快速部署,不過性能上會比編譯型語言稍差;比如 JavaScript、Python ;
編譯型語言:需要通過編譯器將源程式碼編譯成機器碼才能執行;編譯之後如果需要修改程式碼,在執行之前就需要重新編譯。比如 C 語言;
Java 嚴格來說也是編譯型語言,但又介於編譯型和解釋型之間;Java 不直接生成機器碼而是生成中間碼:編譯期間,是將源碼交給編譯器生成 class 文件(位元組碼),這個過程中只做了翻譯的工作,並沒有把程式碼放入記憶體運行;當進入運行期,位元組碼才被 Java 虛擬機載入、解釋成機器語言並運行。

動態語言和靜態語言
動態語言:是指程式在運行時可以改變自身結構,在運行時確定數據類型,一個對象是否能執行某操作,只取決於它有沒有對應的方法,而不在乎它是否是某種類型的對象;比如 JavaScript、Python。
靜態語言:相對於動態語言來說,在編譯時變數的數據類型就已經確定(使用變數之前必須聲明數據類型),在編譯時就會進行類型是否匹配;比如 C 語言、Java ;
反射的概念
Java 反射機制:在運行過程中,對於任意一個類,都能知道其所有的屬性和方法;對於任意一個對象,都能調用其屬性和方法;這種動態獲取類資訊和調用對象方法的功能,就是 Java 反射機制。
既然反射裡面有一個「反」字,那麼我們先看看何為「正」。
在 Java 中,要使用一個類中的某個方法,「正向」都是這樣的:
ArrayList list = new ArrayList(); //實例化 list.add("reflection"); //執行方法那麼反向(反射)要如何實現?
Class clz = Class.forName("java.util.ArrayList"); Method method_add = clz.getMethod("add",Object.class); Constructor constructor = clz.getConstructor(); Object object = constructor.newInstance(); method_add.invoke(object, "reflection"); Method method_get = clz.getMethod("get",int.class); System.out.println(method_get.invoke(object, 0));兩段程式碼執行的結果是一樣的,但是「正向」程式碼在編譯前,就已經明確了要運行的類是什麼(ArrayList),而第二段程式碼,只有在程式碼運行時,才知道運行的類是 java.util.ArrayList。
反射的作用
講到這裡,有些同學可能會有疑問:「反射有什麼用?我明明都已經知道了要使用的類是 ArrayList ,我不能直接 new 一個對象然後執行裡面的方法么?」
當然可以!不過很多場景中,在程式碼運行之前並不知道需要使用哪個類,或者說在運行的時候才決定使用哪個類;
比如有這麼一個功能:「調用阿里雲的人臉識別 API 」;這還不簡單,參考對方的 API 文檔,很快就能實現。

faceRecognition(Object faceImg){ //調用阿里雲的人臉識別 API }上線一個月後,領導說:「咱公司開始和騰訊雲合作了,人臉識別的介面改一下吧」。

faceRecognition(Object faceImg){ //調用騰訊雲的人臉識別 API }修改上線運行了兩個月,領導說:「換回來吧」... ...
當然有聰明的程式設計師會想到設置一個開關配置,讓開關決定走哪段程式碼邏輯,如果領導哪天想變成亞馬遜雲的服務,繼續寫 if-else 就好了:
faceRecognition(Object faceImg){ if("AL".equals(configStr)){ //調用阿里雲的人臉識別 API }else if("TX".equals(configStr)){ //調用騰訊雲的人臉識別 API }else if("AM".equals(configStr)){ //調用亞馬遜雲的人臉識別 API } }不過還有一種更好的方法:
1. 定義一個介面:
interface FaceRecognitionInterface(){ faceRecognition(Object faceImg) ; }2. 多個實現類:
class ALFaceRecognition implements FaceRecognitionInterface{ //調用阿里雲的人臉識別 API 的實現}class TXFaceRecognition implements FaceRecognitionInterface{ //調用騰訊雲的人臉識別 API 的實現}3. 在調用人臉識別功能的程式碼中:
String configStr = "讀取配置,走阿里雲還是騰訊雲"; FaceRecognitionInterface faceRe = Class.forName(configStr).newInstance(); faceRe.faceRecognition(faceImg);
如果上面這個例子,你依然覺得在調用方法中做 if-else 判斷,和使用反射實現並沒有差太多,但是如果程式設計師 A 提供介面,程式設計師 B 提供實現,程式設計師 C 寫客戶端呢?
回憶一下 JDBC 的使用,比如創建一個連接:
public Connection getConnection() throws Exception{ Connection conn = null; //初始化驅動類 Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://url","root", "admin"); return conn;}其中:
- 程式設計師 A 提供介面:Oracle 公司(之前的 Sun)提供 JDBC 標準(介面)。
- 程式設計師 B 提供實現:各個資料庫廠商提供針對自家資料庫的實現。
- 程式設計師 C 寫客戶端:我等碼農在 Java 中敲程式碼訪問資料庫。
總結一下Java 反射的作用:可以設計出更為通用和靈活的架構,很多框架為了保證其通用性,可以根據配置載入不用的類,這時候要用到反射。除此之外:
- 動態代理:在不改變目標對象方法的情況下對方法進行增強,比如使用 AOP 攔截某些方法列印日誌,這就需要通過反射執行方法中的內容。
- 註解:利用反射機制,獲取註解並執行對應的行為。
用反射的用法
上文中我們知道了 Java 運行期的源文件是 class 文件(位元組碼),所以要使用反射,那麼就需要獲取到位元組碼文件對象,在 Java 中,獲取位元組碼文件對象有三種方式:
- 調用某個類的 class 屬性:類名.class
- 調用對象的 getClass() 方法:對象.getClass()
- 使用 Class 類中的 forName() 靜態方法:Class.forName(類的全路徑) ,建議使用這種方法
java.lang.reflect 類庫提供了對反射的支援:
- Field :可以使用 get 和 set 方法讀取和修改對象的屬性;
- Method :可以使用 invoke() 方法調用對象中的方法;
- Constructor :可以用 newInstance() 創建新的對象。
反射的優缺點
優點:在運行時動態獲取類和對象中的內容,極大地提高系統的靈活性和擴展性;誇張一些說,反射是框架設計的靈魂。
缺點:會有一定的性能損耗,JVM 無法對這些程式碼進行優化;破壞類的封裝性。
總之,可能大家在平時的開發過程中,感覺自己並沒有寫過反射相關的程式碼,但是在我們用到的各種開源框架中,反射無處不在。