反射 (Reflection) 是 Java 的特征之一,它允許運(yùn)行中的 Java 程序獲取自身的信息,并且可以操作類或?qū)ο蟮膬?nèi)部屬性。
簡(jiǎn)而言之,通過反射,我們可以在運(yùn)行時(shí)獲得程序或程序集中每一個(gè)類型的成員和成員的信息。程序中一般的對(duì)象的類型都是在編譯期就確定下來的,而 Java 反射機(jī)制可以動(dòng)態(tài)地創(chuàng)建對(duì)象并調(diào)用其屬性,這樣的對(duì)象的類型在編譯期是未知的。所以我們可以通過反射機(jī)制直接創(chuàng)建對(duì)象,即使這個(gè)對(duì)象的類型在編譯期是未知的。
反射的核心:是 JVM 在運(yùn)行時(shí)才動(dòng)態(tài)加載類或調(diào)用方法/訪問屬性,它不需要事先(寫代碼的時(shí)候或編譯期)知道運(yùn)行對(duì)象是誰。
Java 反射主要提供以下功能:
在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類;
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象;
在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法(反射甚至可以調(diào)用private方法);
在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法
重點(diǎn):是運(yùn)行時(shí)而不是編譯時(shí)
二、反射的主要用途
很多人都認(rèn)為反射在實(shí)際的 Java 開發(fā)應(yīng)用中并不廣泛,其實(shí)不然。當(dāng)我們?cè)谑褂?IDE(如 Eclipse,IDEA)時(shí),當(dāng)我們輸入一個(gè)對(duì)象或類并想調(diào)用它的屬性或方法時(shí),一按點(diǎn)號(hào),編譯器就會(huì)自動(dòng)列出它的屬性或方法,這里就會(huì)用到反射。
反射最重要的用途就是開發(fā)各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 Bean),為了保證框架的通用性,它們可能需要根據(jù)配置文件加載不同的對(duì)象或類,調(diào)用不同的方法,這個(gè)時(shí)候就必須用到反射,運(yùn)行時(shí)動(dòng)態(tài)加載需要加載的對(duì)象。
舉一個(gè)例子,在運(yùn)用 Struts 2 框架的開發(fā)中我們一般會(huì)在 struts.xml 里去配置 Action,比如:
<action name="login"
class="org.ScZyhSoft.test.action.SimpleLoginAction"
method="execute">
<result>/shop/shop-index.jsp</result>
<result name="error">login.jsp</result>
</action>
配置文件與 Action 建立了一種映射關(guān)系,當(dāng) View 層發(fā)出請(qǐng)求時(shí),請(qǐng)求會(huì)被 StrutsPrepareAndExecuteFilter 攔截,然后 StrutsPrepareAndExecuteFilter 會(huì)去動(dòng)態(tài)地創(chuàng)建 Action 實(shí)例。比如我們請(qǐng)求 login.action,那么 StrutsPrepareAndExecuteFilter就會(huì)去解析struts.xml文件,檢索action中name為login的Action,并根據(jù)class屬性創(chuàng)建SimpleLoginAction實(shí)例,并用invoke方法來調(diào)用execute方法,這個(gè)過程離不開反射。
對(duì)與框架開發(fā)人員來說,反射雖小但作用非常大,它是各種容器實(shí)現(xiàn)的核心。而對(duì)于一般的開發(fā)者來說,不深入框架開發(fā)則用反射用的就會(huì)少一點(diǎn),不過了解一下框架的底層機(jī)制有助于豐富自己的編程思想,也是很有益的。
三、反射的基本運(yùn)用
上面我們提到了反射可以用于判斷任意對(duì)象所屬的類,獲得 Class 對(duì)象,構(gòu)造任意一個(gè)對(duì)象以及調(diào)用一個(gè)對(duì)象。這里我們介紹一下基本反射功能的使用和實(shí)現(xiàn)(反射相關(guān)的類一般都在 java.lang.relfect 包里)。
1、獲得 Class 對(duì)象
方法有三種:
(1) 使用 Class 類的 forName 靜態(tài)方法:
1 2 3 4 5 6 7 |
public static Class<?> forName(String className) ``` 比如在 JDBC 開發(fā)中常用此方法加載數(shù)據(jù)庫驅(qū)動(dòng): ```java Class.forName(driver); |
(2)直接獲取某一個(gè)對(duì)象的 class,比如:
1 2 |
Class<?> klass = int.class; Class<?> classInt = Integer.TYPE; |
(3)調(diào)用某個(gè)對(duì)象的 getClass() 方法,比如:
1 2 |
StringBuilder str = new StringBuilder("123"); Class<?> klass = str.getClass(); |
2、判斷是否為某個(gè)類的實(shí)例
一般地,我們用 instanceof 關(guān)鍵字來判斷是否為某個(gè)類的實(shí)例。同時(shí)我們也可以借助反射中 Class 對(duì)象的 isInstance() 方法來判斷是否為某個(gè)類的實(shí)例,它是一個(gè) native 方法:
1 |
public native boolean isInstance(Object obj); |
3、創(chuàng)建實(shí)例
通過反射來生成對(duì)象主要有兩種方式。
使用Class對(duì)象的newInstance()方法來創(chuàng)建Class對(duì)象對(duì)應(yīng)類的實(shí)例。
1 2 |
Class<?> c = String.class; Object str = c.newInstance(); |
先通過Class對(duì)象獲取指定的Constructor對(duì)象,再調(diào)用Constructor對(duì)象的newInstance()方法來創(chuàng)建實(shí)例。這種方法可以用指定的構(gòu)造器構(gòu)造類的實(shí)例。
1 2 3 4 5 6 7 |
//獲取String所對(duì)應(yīng)的Class對(duì)象 Class<?> c = String.class; //獲取String類帶一個(gè)String參數(shù)的構(gòu)造器 Constructor constructor = c.getConstructor(String.class); //根據(jù)構(gòu)造器創(chuàng)建實(shí)例 Object obj = constructor.newInstance("23333"); System.out.println(obj); |
4、獲取方法
獲取某個(gè)Class對(duì)象的方法集合,主要有以下幾個(gè)方法:
getDeclaredMethods 方法返回類或接口聲明的所有方法,包括公共、保護(hù)、默認(rèn)(包)訪問和私有方法,但不包括繼承的方法。
1 |
public Method[] getDeclaredMethods() throws SecurityException |
getMethods 方法返回某個(gè)類的所有公用(public)方法,包括其繼承類的公用方法。
1 |
public Method[] getMethods() throws SecurityException |
getMethod 方法返回一個(gè)特定的方法,其中第一個(gè)參數(shù)為方法名稱,后面的參數(shù)為方法的參數(shù)對(duì)應(yīng)Class的對(duì)象。
1 |
public Method getMethod(String name, Class<?>... parameterTypes) |
5、獲取構(gòu)造器信息
獲取類構(gòu)造器的用法與上述獲取方法的用法類似。主要是通過Class類的getConstructor方法得到Constructor類的一個(gè)實(shí)例,而Constructor類有一個(gè)newInstance方法可以創(chuàng)建一個(gè)對(duì)象實(shí)例:
1 |
public T newInstance(Object ... initargs) |
此方法可以根據(jù)傳入的參數(shù)來調(diào)用對(duì)應(yīng)的Constructor創(chuàng)建對(duì)象實(shí)例。
6、獲取類的成員變量(字段)信息
主要是這幾個(gè)方法,在此不再贅述:
getFiled:訪問公有的成員變量
getDeclaredField:所有已聲明的成員變量,但不能得到其父類的成員變量getFileds 和 getDeclaredFields 方法用法同上(參照 Method)。
7、調(diào)用方法
當(dāng)我們從類中獲取了一個(gè)方法后,我們就可以用 invoke() 方法來調(diào)用這個(gè)方法。invoke 方法的原型為:
1 2 3 |
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException |
四、反射的一些注意事項(xiàng)
由于反射會(huì)額外消耗一定的系統(tǒng)資源,因此如果不需要?jiǎng)討B(tài)地創(chuàng)建一個(gè)對(duì)象,那么就不需要用反射。另外,反射調(diào)用方法時(shí)可以忽略權(quán)限檢查,因此可能會(huì)破壞封裝性而導(dǎo)致安全問題。
更多關(guān)于“java培訓(xùn)”的問題,歡迎咨詢千鋒教育在線名師。千鋒教育多年辦學(xué),課程大綱緊跟企業(yè)需求,更科學(xué)更嚴(yán)謹(jǐn),每年培養(yǎng)泛IT人才近2萬人。不論你是零基礎(chǔ)還是想提升,都可以找到適合的班型,千鋒教育隨時(shí)歡迎你來試聽。