天天看點

【Java】反射

反射就是在運作時才知道要操作的類是什麼,并且可以在運作時擷取類的完整構造,并調用對應的方法。

一直說反射反射,但是為什麼要反射卻不甚了解。“明明直接 <code>new</code> 一個對象就可以,為什麼還需要使用反射呢?”

反射庫

反射就是圍繞着 <code>Class</code> 對象和 <code>java.lang.reflect</code> 類庫的,就是各種的 <code>API</code> 調用。

涉及的類:

作用

Class

描述類的資訊

Constructor

構造器類

Method

方法類

Field

字段類

涉及的方法:

方法名

getXXX()

擷取公有的構造器、方法、屬性

getDeclaredXXX()

擷取所有的構造器、方法、屬性

掌握以下幾種差不多就入門了:

知道擷取 <code>Class</code> 對象的三種途徑;

通過 <code>Class</code> 對象建立出對象,擷取到構造器、方法、屬性;

通過反射的 <code>API</code> 修改屬性的值、調用方法。

<code>Class</code> 對象

我們肯定都碰到過類型強轉失敗(<code>ClassCastException</code>)的情況,那麼為什麼編譯時能通過,運作時卻報錯呢?<code>JVM</code> 是怎麼知道類型不比對的呢?實際上它是通過 <code>Class</code> 對象來判斷的。

<code>.java</code> 檔案經過 <code>javac</code> 指令編譯成 <code>.class</code> 檔案,當我們執行了初始化操作( <code>new</code>、類初始化時父類也一同被初始化、反射等)後,會由類加載器(雙親委派模型)将 <code>.class</code> 檔案内容加載到方法區中,并在 <code>Java</code> 堆中建立一個 <code>java.lang.Class</code> 類的對象,這個 <code>Class</code> 對象代表着類相關的資訊。

既然說,<code>Class</code> 對象代表着類相關的資訊,那說明隻要類有什麼東西(構造器、方法、屬性),在 <code>Class</code> 對象裡都能找得到,可以在 <code>IDEA</code> 裡面檢視 <code>java.lang.Class</code> 類包含哪些方法和屬性。

擷取 <code>Class</code> 對象的三種途徑:

通過控制台列印可知:在運作期間,一個類,隻有一個 <code>Class</code> 對象産生。

三種方式的差別:

第一種,對象都有了還要反射幹什麼;

第二種,需要導入類所在的包,依賴太強,不導包就編譯錯誤;

第三種,類全限定名字元串可以作為參數傳入,也可從配置檔案(常用)中讀取。

示例

<code>JDBC</code> 的代碼(寫死):

後來為什麼要改成下面的形式呢:

理由很簡單,我們不想修改代碼,把需要變動的内容寫進配置檔案,不香嗎?但凡有一天,我們的 <code>username</code>,<code>password</code>,<code>url</code> 甚至是資料庫都改了,我們都能夠通過修改配置的方式去實作。不需要動絲毫的代碼,改下配置就完事了,這就能提供程式的靈活性。修改代碼的風險和代價比修改配置大,即使不知道代碼的實作,都能通過修改配置來完成要做的事。

像這種通過修改配置檔案來進行動态響應的,其内部很可能就是通過反射來做的。

<code>Servlet</code> 開發是這樣擷取頁面參數的,一堆的 <code>getParameter()</code> 模闆代碼:

而 <code>Spring MVC</code> 是這樣擷取頁面參數的:

為什麼我們寫上 <code>JavaBean</code>,保持字段名與參數名相同,就能 “自動” 得到對應的值呢,其實就是通過反射來做的。

現有系統:

<code>Student</code> 類:

配置檔案 <code>Student.properties</code>:

測試類:

需求:

當我們更新這個系統,需要 <code>main()</code> 方法列印其他内容時,不需要修改 <code>Student</code> 類,新寫一個 <code>Student2</code> 的類,并将 <code>Student.properties</code> 檔案的内容修改一下就可以了。既存代碼就一點不用改動,這也符合對擴充開放、對修改關閉的開閉原則。

要替換的 <code>Student2</code> 類:

配置檔案更改為:

泛型用在編譯期,編譯過後泛型擦除(可以通過 <code>ParameterizedType</code> 擷取泛型類型)成 <code>Object</code> 或上限類型(父類類型)。是以是可以通過反射越過泛型檢查的,一般不會這樣使用,除非自己需要一些特殊的操作。

為什麼要使用反射?

通過上面幾個示例,我們可以知道為什麼要使用反射了:

提高程式的靈活性,并符合開閉原則,如 <code>JDBC</code>、通過反射運作配置檔案内容;

屏蔽掉實作的細節,更友善使用,如 <code>Spring MVC</code> 中頁面參數與 <code>JavaBean</code> 的映射。

我們寫業務代碼是用不到反射的,自己去寫元件才會用到反射。

當然,如果自定義注解的話,那麼是需要用到反射對注解做相應處理的。

參考:

java 的反射到底是有什麼用處?怎麼用? - Java3y

Java 基礎之 — 反射(非常重要)