【轉(zhuǎn)】深入解析Java反射

鏈接:https://www.sczyh30.com/posts/Java/java-reflection-1/#%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%8D%E5%B0%84%EF%BC%9F

一窃页、回顧:什么是反射呜袁?

反射 (Reflection) 是 Java 的特征之一,它允許運行中的 Java 程序獲取自身的信息鸡捐,并且可以操作類或?qū)ο蟮膬?nèi)部屬性醇蝴。

Oracle 官方對反射的解釋是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

簡而言之,通過反射贡茅,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的传于,而 Java 反射機制可以動態(tài)地創(chuàng)建對象并調(diào)用其屬性,這樣的對象的類型在編譯期是未知的醉顽。所以我們可以通過反射機制直接創(chuàng)建對象沼溜,即使這個對象的類型在編譯期是未知的。

反射的核心是 JVM 在運行時才動態(tài)加載類或調(diào)用方法/訪問屬性游添,它不需要事先(寫代碼的時候或編譯期)知道運行對象是誰盛末。

Java 反射主要提供以下功能:

在運行時判斷任意一個對象所屬的類;

在運行時構造任意一個類的對象否淤;

在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調(diào)用private方法)悄但;

在運行時調(diào)用任意一個對象的方法

重點:是運行時而不是編譯時

二、反射的主要用途

很多人都認為反射在實際的 Java 開發(fā)應用中并不廣泛石抡,其實不然檐嚣。當我們在使用 IDE(如 Eclipse,IDEA)時啰扛,當我們輸入一個對象或類并想調(diào)用它的屬性或方法時嚎京,一按點號,編譯器就會自動列出它的屬性或方法隐解,這里就會用到反射鞍帝。

反射最重要的用途就是開發(fā)各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 Bean)煞茫,為了保證框架的通用性帕涌,它們可能需要根據(jù)配置文件加載不同的對象或類,調(diào)用不同的方法续徽,這個時候就必須用到反射蚓曼,運行時動態(tài)加載需要加載的對象。

舉一個例子钦扭,在運用 Struts 2 框架的開發(fā)中我們一般會在?struts.xml?里去配置?Action纫版,比如:

class="org.ScZyhSoft.test.action.SimpleLoginAction"

method="execute">

/shop/shop-index.jsp

login.jsp

配置文件與?Action?建立了一種映射關系,當 View 層發(fā)出請求時客情,請求會被?StrutsPrepareAndExecuteFilter?攔截其弊,然后?StrutsPrepareAndExecuteFilter?會去動態(tài)地創(chuàng)建 Action 實例。比如我們請求?login.action膀斋,那么?StrutsPrepareAndExecuteFilter就會去解析struts.xml文件梭伐,檢索action中name為login的Action,并根據(jù)class屬性創(chuàng)建SimpleLoginAction實例概页,并用invoke方法來調(diào)用execute方法籽御,這個過程離不開反射练慕。

對與框架開發(fā)人員來說惰匙,反射雖小但作用非常大技掏,它是各種容器實現(xiàn)的核心。而對于一般的開發(fā)者來說项鬼,不深入框架開發(fā)則用反射用的就會少一點哑梳,不過了解一下框架的底層機制有助于豐富自己的編程思想,也是很有益的绘盟。

三鸠真、反射的基本運用

上面我們提到了反射可以用于判斷任意對象所屬的類,獲得 Class 對象龄毡,構造任意一個對象以及調(diào)用一個對象吠卷。這里我們介紹一下基本反射功能的使用和實現(xiàn)(反射相關的類一般都在 java.lang.relfect 包里)。

1沦零、獲得 Class 對象

方法有三種:

(1) 使用 Class 類的?forName?靜態(tài)方法:

publicstaticClass forName(String className)

```

比如在 JDBC 開發(fā)中常用此方法加載數(shù)據(jù)庫驅(qū)動:

```java

Class.forName(driver);

(2)直接獲取某一個對象的 class祭隔,比如:

Class klass =int.class;

Class<?> classInt = Integer.TYPE;

(3)調(diào)用某個對象的?getClass()?方法,比如:

StringBuilder str =newStringBuilder("123");

Class<?> klass = str.getClass();

2路操、判斷是否為某個類的實例

一般地疾渴,我們用?instanceof?關鍵字來判斷是否為某個類的實例。同時我們也可以借助反射中 Class 對象的?isInstance()?方法來判斷是否為某個類的實例屯仗,它是一個 native 方法:

public native boolean isInstance(Object obj);

3搞坝、創(chuàng)建實例

通過反射來生成對象主要有兩種方式。

使用Class對象的newInstance()方法來創(chuàng)建Class對象對應類的實例魁袜。

Class<?> c = String.class;

Object str = c.newInstance();

先通過Class對象獲取指定的Constructor對象桩撮,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建實例。這種方法可以用指定的構造器構造類的實例峰弹。

//獲取String所對應的Class對象

Class<?> c = String.class;

//獲取String類帶一個String參數(shù)的構造器

Constructor constructor = c.getConstructor(String.class);

//根據(jù)構造器創(chuàng)建實例

Object obj = constructor.newInstance("23333");

System.out.println(obj);

4距境、獲取方法

獲取某個Class對象的方法集合,主要有以下幾個方法:

getDeclaredMethods?方法返回類或接口聲明的所有方法垮卓,包括公共垫桂、保護、默認(包)訪問和私有方法粟按,但不包括繼承的方法诬滩。

1publicMethod[] getDeclaredMethods()throwsSecurityException

getMethods?方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法灭将。

1publicMethod[] getMethods()throwsSecurityException

getMethod?方法返回一個特定的方法疼鸟,其中第一個參數(shù)為方法名稱,后面的參數(shù)為方法的參數(shù)對應Class的對象庙曙。

1publicMethodgetMethod(String name, Class<?>... parameterTypes)

只是這樣描述的話可能難以理解空镜,我們用例子來理解這三個方法:

packageorg.ScZyhSoft.common;

importjava.lang.reflect.InvocationTargetException;

importjava.lang.reflect.Method;

publicclasstest1{

publicstaticvoidtest()throwsIllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException{

? ? ? ? Class<?> c = methodClass.class;

? ? ? ? Object object = c.newInstance();

? ? ? ? Method[] methods = c.getMethods();

? ? ? ? Method[] declaredMethods = c.getDeclaredMethods();

//獲取methodClass類的add方法

Method method = c.getMethod("add",int.class,int.class);

//getMethods()方法獲取的所有方法

System.out.println("getMethods獲取的方法:");

for(Method m:methods)

? ? ? ? ? ? System.out.println(m);

//getDeclaredMethods()方法獲取的所有方法

System.out.println("getDeclaredMethods獲取的方法:");

for(Method m:declaredMethods)

? ? ? ? ? ? System.out.println(m);

? ? }

? ? }

classmethodClass{

publicfinalintfuck =3;

publicintadd(inta,intb){

returna+b;

? ? }

publicintsub(inta,intb){

returna+b;

? ? }

}

程序運行的結果如下:

getMethods獲取的方法:

public int org.ScZyhSoft.common.methodClass.add(int,int)

public int org.ScZyhSoft.common.methodClass.sub(int,int)

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

getDeclaredMethods獲取的方法:

public int org.ScZyhSoft.common.methodClass.add(int,int)

public int org.ScZyhSoft.common.methodClass.sub(int,int)

可以看到,通過?getMethods()?獲取的方法可以獲取到父類的方法,比如 java.lang.Object 下定義的各個方法。

5吴攒、獲取構造器信息

獲取類構造器的用法與上述獲取方法的用法類似张抄。主要是通過Class類的getConstructor方法得到Constructor類的一個實例,而Constructor類有一個newInstance方法可以創(chuàng)建一個對象實例:

1publicTnewInstance(Object ... initargs)

此方法可以根據(jù)傳入的參數(shù)來調(diào)用對應的Constructor創(chuàng)建對象實例洼怔。

6署惯、獲取類的成員變量(字段)信息

主要是這幾個方法,在此不再贅述:

getFiled:訪問公有的成員變量

getDeclaredField:所有已聲明的成員變量镣隶,但不能得到其父類的成員變量

getFileds?和?getDeclaredFields?方法用法同上(參照 Method)极谊。

7、調(diào)用方法

當我們從類中獲取了一個方法后安岂,我們就可以用?invoke()?方法來調(diào)用這個方法轻猖。invoke?方法的原型為:

publicObjectinvoke(Object obj, Object... args)

throwsIllegalAccessException, IllegalArgumentException,

? ? ? ? ? InvocationTargetException

下面是一個實例:

publicclasstest1{

publicstaticvoidmain(String[] args)throwsIllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException{

? ? ? ? Class<?> klass = methodClass.class;

//創(chuàng)建methodClass的實例

? ? ? ? Object obj = klass.newInstance();

//獲取methodClass類的add方法

Method method = klass.getMethod("add",int.class,int.class);

//調(diào)用method對應的方法 => add(1,4)

Object result = method.invoke(obj,1,4);

? ? ? ? System.out.println(result);

? ? }

}

classmethodClass{

publicfinalintfuck =3;

publicintadd(inta,intb){

returna+b;

? ? }

publicintsub(inta,intb){

returna+b;

? ? }

}

關于?invoke?方法的詳解,后面我會專門寫一篇文章來深入解析 invoke 的過程域那。

8蜕依、利用反射創(chuàng)建數(shù)組

數(shù)組在Java里是比較特殊的一種類型,它可以賦值給一個Object Reference琉雳。下面我們看一看利用反射創(chuàng)建數(shù)組的例子:

publicstaticvoidtestArray()throwsClassNotFoundException{

Class cls = Class.forName("java.lang.String");

Object array = Array.newInstance(cls,25);

//往數(shù)組里添加內(nèi)容

Array.set(array,0,"hello");

Array.set(array,1,"Java");

Array.set(array,2,"fuck");

Array.set(array,3,"Scala");

Array.set(array,4,"Clojure");

//獲取某一項的內(nèi)容

System.out.println(Array.get(array,3));

? ? }

其中的Array類為java.lang.reflect.Array類样眠。我們通過Array.newInstance()創(chuàng)建數(shù)組對象,它的原型是:

publicstaticObjectnewInstance(Class componentType,intlength)

throwsNegativeArraySizeException {

returnnewArray(componentType, length);

? ? }

而?newArray?方法是一個 native 方法翠肘,它在 HotSpot JVM 里的具體實現(xiàn)我們后邊再研究檐束,這里先把源碼貼出來:

privatestaticnativeObjectnewArray(Class componentType,intlength)

throwsNegativeArraySizeException;

源碼目錄:openjdk\hotspot\src\share\vm\runtime\reflection.cpp

arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {

if(element_mirror ==NULL) {

? ? THROW_0(vmSymbols::java_lang_NullPointerException());

? }

if(length <0) {

? ? THROW_0(vmSymbols::java_lang_NegativeArraySizeException());

? }

if(java_lang_Class::is_primitive(element_mirror)) {

? ? Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);

returnTypeArrayKlass::cast(tak)->allocate(length, THREAD);

}else{

? ? Klass* k = java_lang_Class::as_Klass(element_mirror);

if(k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {

? ? ? THROW_0(vmSymbols::java_lang_IllegalArgumentException());

? ? }

returnoopFactory::new_objArray(k, length, THREAD);

? }

}

另外,Array 類的?set?和?get?方法都為 native 方法束倍,在 HotSpot JVM 里分別對應?Reflection::array_set?和?Reflection::array_get?方法被丧,這里就不詳細解析了。

四绪妹、反射的一些注意事項

由于反射會額外消耗一定的系統(tǒng)資源甥桂,因此如果不需要動態(tài)地創(chuàng)建一個對象,那么就不需要用反射邮旷。

另外黄选,反射調(diào)用方法時可以忽略權限檢查,因此可能會破壞封裝性而導致安全問題婶肩。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末办陷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子律歼,更是在濱河造成了極大的恐慌民镜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件险毁,死亡現(xiàn)場離奇詭異制圈,居然都是意外死亡们童,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門鲸鹦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慧库,“玉大人,你說我怎么就攤上這事亥鬓。” “怎么了域庇?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵嵌戈,是天一觀的道長。 經(jīng)常有香客問我听皿,道長熟呛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任尉姨,我火速辦了婚禮庵朝,結果婚禮上,老公的妹妹穿的比我還像新娘又厉。我一直安慰自己九府,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布覆致。 她就那樣靜靜地躺著侄旬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煌妈。 梳的紋絲不亂的頭發(fā)上儡羔,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音璧诵,去河邊找鬼汰蜘。 笑死,一個胖子當著我的面吹牛之宿,可吹牛的內(nèi)容都是我干的族操。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼比被,長吁一口氣:“原來是場噩夢啊……” “哼坪创!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姐赡,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤莱预,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后项滑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體依沮,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了危喉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宋渔。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辜限,靈堂內(nèi)的尸體忽然破棺而出皇拣,到底是詐尸還是另有隱情,我是刑警寧澤薄嫡,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布氧急,位于F島的核電站,受9級特大地震影響毫深,放射性物質(zhì)發(fā)生泄漏吩坝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一哑蔫、第九天 我趴在偏房一處隱蔽的房頂上張望钉寝。 院中可真熱鬧,春花似錦闸迷、人聲如沸嵌纲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疹瘦。三九已至,卻和暖如春巡球,著一層夾襖步出監(jiān)牢的瞬間言沐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工酣栈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留险胰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓矿筝,卻偏偏與公主長得像起便,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窖维,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容