JAVA中的糕富帥技術(shù)——反射(一)
今天就來談?wù)劮瓷錂C(jī)制;學(xué)過JAVA的人不一定懂得反射,但是一定聽說過反射,不過也僅僅是聽說過而已;因?yàn)榉瓷溆玫牡胤揭膊粫敲炊啵欠瓷溆玫拿罱?jīng)常會解決我們撓破頭皮的大問題。至于諸如為什么叫做反射、而不叫做正射倒射此類的歷史問題,還是交給歷史學(xué)家去研究吧。。。
反射的基石
在談反射之前,我們應(yīng)該先了解下類的概念來引入。類是一種抽象的概念,舉個(gè)例子“我爸是李剛我爸李雙江”,從這句話中我們發(fā)現(xiàn)有李剛、李雙江這兩個(gè)人,我們來抽象它們的特點(diǎn),我們發(fā)現(xiàn)它們都像人。沒錯,那么我們就可以將人作為它們的一個(gè)抽象,反過來說李剛和李雙江就是人的一個(gè)具體實(shí)例;所以我們可以用一個(gè)Person類代表人來表示這種抽象。既然理解了類的概念,那些年那些陪我們度過日日夜夜的java類們,我們是不是也應(yīng)該抽象出一個(gè)類來證明一下他們,沒錯,那就是Class了!
Class就是java類的抽象,它抽象出了java的共性,如類的名字、類的構(gòu)造方法、類的成員變量、類的老爸、類的方法等等等。既然這么說,那么我們通過這個(gè)Class,我們就可以得到這個(gè)類的方方面面的信息、興許還能比查戶口還詳細(xì)呢。我們創(chuàng)建出的每一個(gè)類,例如person類,說到底也就是我們實(shí)例化了一個(gè)Class的實(shí)例,來保存person類的名字、變量、方法這些信息,在內(nèi)存中表示就是保存了person類的字節(jié)碼,如果你理解了這些并且接受了我的看法,那么咱們有共同語言,可以繼續(xù)往下說。
既然person類有擁有自己的字節(jié)碼,那么我們可以獲取到這個(gè)字節(jié)碼嗎?答案是肯定的,而且還不止一種方法。參見代碼:
public static void main(String[] args) throws Exception {//第一種方法,直接通過Person類來獲取字節(jié)碼 Class cls1 = Person.class;//第二種方法,通過類的實(shí)例來獲取Person類的字節(jié)碼 Person person = new Person();Class cls2 = person.getClass();//第三種方法,調(diào)用Class類的靜態(tài)方法來獲取對應(yīng)類的字節(jié)碼,該方法會拋出異常 Class cls3 = Class.forName('Person');}
從代碼中看,我們可以斷定:Person類的字節(jié)碼就是Class的具體實(shí)例;我們也可以猜到,至于類的字節(jié)碼有包含什么東東,大家盡管猜吧,后面我會慢慢講解。我們再來看看下面的代碼:
System.out.println(cls1 == cls2);System.out.println(cls2 == cls3);
我們運(yùn)行程序,會發(fā)現(xiàn)輸出了:
truetrue
這三個(gè)玩意竟然是同一個(gè)東西,那么就很好解釋了:在java的虛擬機(jī)中,每一個(gè)類都會被保存成為一個(gè)字節(jié)碼,用來保存該類的信息如名字、父類、變量、方法等。一個(gè)類的字節(jié)碼在虛擬機(jī)中有且只有一個(gè),也就是在第一次加載該類的時(shí)候會將類的字節(jié)碼加載到j(luò)ava虛擬機(jī)中,而上面有三種方法可以從虛擬機(jī)中獲取類的字節(jié)碼(PS:第三種方法最為常用),但是你別疑惑獲取這個(gè)字節(jié)碼干嘛嘛用,我們要反射嘛,說白了我們就是要來強(qiáng)暴這字節(jié)碼(Class)。。。。。(~ o ~)~zZ
理解反射
既然前面講解了Class類,現(xiàn)在我們可以開始講反射了。反射是什么呢?反射就是將類的各種成分映射成各種類,我們知道一個(gè)java類可以用一個(gè)class的對象來表示,這個(gè)類的組成成分有名字、變量、構(gòu)造方法等信息,我們當(dāng)然可以用一個(gè)個(gè)java類的表示。換句話說,表示java類的Class類提供了一系列的方法給我們用來獲取其中的變量、方法、構(gòu)造方法等信息,這些信息也有相應(yīng)的類的實(shí)例來表示,也就是Field、Method、Constructor等等。或者更通俗的說,F(xiàn)ield就是java類中的所有變量的抽象、同理Method就是java類中所有方法的抽象,如果還是看不懂,很正常,往下看代碼估計(jì)更好理解。
構(gòu)造方法的反射
從前面我們知道,Constructor就是java類所有構(gòu)造方法的抽象。那么我們怎么通過反射來獲取類的構(gòu)造方法呢,參見代碼:
public class Test{ public static void main(String[] args) throws Exception {Class cls = Person.class;//獲取Person類的字節(jié)碼 Constructor constructor1 = cls.getConstructor();//調(diào)用getConstructor()獲取Person無參構(gòu)造方法 Person p1 = (Person) constructor1.newInstance();//通過調(diào)用newInstance()來執(zhí)行無參構(gòu)造方法 Constructor constructor2 = cls.getConstructor(int.class);//調(diào)用getConstructor(*.class)獲取Person帶參構(gòu)造方法 Person p2 = (Person) constructor2.newInstance(1);//通過調(diào)用newInstance(int)來執(zhí)行帶參構(gòu)造方法 }} class Person{ public Person(){System.out.println('無參構(gòu)造方法');} public Person(int i){System.out.println('帶參構(gòu)造方法');}}
控制臺輸出:
無參構(gòu)造方法帶參構(gòu)造方法
這里我們開始講解一下,代碼通過Person.class來獲取Person類的字節(jié)碼并將其保存在一個(gè)Class類的實(shí)例cls中,然后再通過cls.getConstructor()來獲取字節(jié)碼中的構(gòu)造方法并將其放入Constructor的實(shí)例constructor之中,很明顯,這個(gè)constructor并不是Person的構(gòu)造方法,而是保存Person構(gòu)造方法的一個(gè)實(shí)例,所以我們可以通過調(diào)用newInstance()來獲取保存在constructor中的person類的構(gòu)造方法并執(zhí)行,構(gòu)造方法執(zhí)行并返回一個(gè)Object的實(shí)例,并將其強(qiáng)轉(zhuǎn)為Person并保存在person的變量中,這就是調(diào)用反射來獲取構(gòu)造方法生成實(shí)例的全過程。
在代碼中,我們也可以知道怎么獲取帶參的構(gòu)造方法,這是我們需要在getConstructor()是傳入構(gòu)造方法對應(yīng)參數(shù)的字節(jié)碼,例如代碼中Person(int i)我們需要傳入一個(gè)int.class(或者是Integet.TYPE)的字節(jié)碼提供給Class定位需要獲取的構(gòu)造方法。但是如果你比較貪心想獲取全部的構(gòu)造方法,沒問題,通過getConstructors():
Class cls = Person.class;//獲取Person類的字節(jié)碼 Constructor[] constructors = cls.getConstructors();//調(diào)用getConstructor()獲取Person無參構(gòu)造方法 for(Constructor c : constructors){//Person p = c.newInstance(****);遍歷執(zhí)行構(gòu)造方法 }
然后通過for循環(huán),就可以處理你所需要的構(gòu)造方法了。
成員變量的反射
我們說完了構(gòu)造方法的反射,我們就接下來談?wù)劤蓡T變量的反射的用法。慣例還是先看代碼:
public class Test{ public static void main(String[] args) throws Exception {Person p = new Person('小紅', 20);Class cls = Class.forName('com.net168.test.Person');Field fieldName = cls.getField('name'); //fieldNmae的值是小紅嗎?錯!它只是代表Person類身上name的這個(gè)變量,并沒有對應(yīng)到對象身上 // System.out.println(fieldNmae); //fieldNmae不代表具體的值,只代表一個(gè)變量,所以我們需要傳入一個(gè)person實(shí)例才能獲取到其對應(yīng)的值 System.out.println(fieldName.get(p)); }} class Person{ public Person(String name, int age){ this.name = name; this.age = age; } public String name; private int age;//對于某些人來說,年齡是秘密! }
跟構(gòu)造方法的反射的實(shí)現(xiàn)差不多,我們也是先通過獲取Person的字節(jié)碼cls,然后從其中將Person的成員變量映射成一個(gè)Field類,在這里我們將Person.name這個(gè)變量映射成fieldName這個(gè)對象,當(dāng)然我們不可能單純的從fieldName這個(gè)對象中獲取咱們的“小紅”,因?yàn)閒ieldName是從cls中獲取的而并不是從person的實(shí)例中獲取的,所以它值并不是小紅;而是我們可以通過小紅這個(gè)person的實(shí)例p與fieldName聯(lián)系起來,也就是調(diào)用fieldName.get(p)才能獲取小紅這個(gè)字符串。
但是我們?nèi)绻氆@取小紅年齡呢,女人的年齡大多是秘密,私有變量我們也可以這樣獲取嗎?修改下代碼:
Person p = new Person('小紅', 20);Class cls = Class.forName('com.net168.test.Person');Field fieldAge = cls.getField('age');System.out.println(fieldAge.get(p));
執(zhí)行結(jié)果是:
Exception in thread 'main' java.lang.NoSuchFieldException: ageat java.lang.Class.getField(Unknown Source)at com.net168.test.Test.main(Test.java:11)
沒有這個(gè)字段,明明是有這個(gè)age的字段呀!但是我們發(fā)現(xiàn),原來這個(gè)女生的年齡是私有的,她就是不肯告訴咱們啊,那怎么辦?她不想告訴我們,我們就沒法知道了嗎?屌絲是不會那么容易屈服的!所以我們可以稍作一點(diǎn)處理,如下:
Person p = new Person('小紅', 20);Class cls = Class.forName('com.net168.test.Person');Field fieldAge = cls.getDeclaredField('age');//獲取類的私有變量 fieldAge.setAccessible(true);//設(shè)置該私有變量可被外面訪問 System.out.println(fieldAge.get(p));
可以通過getDeclaredField()來獲取Person類的私有變量,而且我們還可以在獲取到外界看不到的私有變量后,再通過setAccessible(true)設(shè)置該私有變量可以被強(qiáng)制訪問。暴力吧,JAVA的反射也被有些人叫做暴力反射。。。運(yùn)行代碼,我們就知道了小紅的芳齡 了:20
成員方法的反射
如果大家看懂了前面成員變量和構(gòu)造方法的反射,基本上再了解成員方法的反射就沒有什么困難了,不賣關(guān)子,還是先上下代碼:
public class Test{ public static void main(String[] args) throws Exception {Person p = new Person();Class cls = p.getClass();//獲取Person的字節(jié)碼 //獲取setName()方法,需要傳入?yún)?shù)為String Method method1 = cls.getMethod('setName', String.class);method1.invoke(p, '小明');//關(guān)聯(lián)p,輸入“小明”并執(zhí)行該方法 //獲取getName()方法,無參則設(shè)為null Method method2 = cls.getMethod('getName', null);String name = (String) method2.invoke(p, null);//invoke返回的類型為Object System.out.println(name); //獲取靜態(tài)方法,由于靜態(tài)方法只依賴與類,所以不需要提供具體的實(shí)例 Method method3 = cls.getMethod('show', int.class); // method3.invoke(p, 1);提供具體實(shí)例p也可通過編譯 method3.invoke(null, 1); }} class Person{ public String name; public String getName(){ return name; } public void setName(String name){ this.name = name;System.out.println('設(shè)置name值為:' + name); } public static void show(int i){System.out.println('這是一個(gè)靜態(tài)方法:' + i); }}
程序運(yùn)行結(jié)果:
設(shè)置name值為:小明小明這是一個(gè)靜態(tài)方法:1
在方法的反射中,我們是利用了Method這個(gè)類,由于跟構(gòu)造方法類似,所以我不就再就獲取有參無參的方法的不同之處進(jìn)行講解。總體來說,就是通過Person的字節(jié)碼獲取到Person類中對應(yīng)的方法并將其保存到Method的一個(gè)對象中,然后通過這個(gè)對象跟Person的具體實(shí)例進(jìn)行搭配,通過invoke()就可以調(diào)用到具體實(shí)例的對應(yīng)方法。在這里我們需要注意的時(shí)靜態(tài)方法的反射,由于靜態(tài)方法屬于一個(gè)類并不是屬于特定的一個(gè)對象,所以我們在調(diào)用靜態(tài)方法的invoke()時(shí),并不需要傳入一個(gè)對象,當(dāng)然你非要傳入一個(gè)具體的實(shí)例也是沒有關(guān)系的,答案依然正確。
相關(guān)文章:
1. PHP循環(huán)與分支知識點(diǎn)梳理2. 讀大數(shù)據(jù)量的XML文件的讀取問題3. 利用CSS3新特性創(chuàng)建透明邊框三角4. 解析原生JS getComputedStyle5. JSP+Servlet實(shí)現(xiàn)文件上傳到服務(wù)器功能6. ASP基礎(chǔ)入門第三篇(ASP腳本基礎(chǔ))7. css代碼優(yōu)化的12個(gè)技巧8. ASP刪除img標(biāo)簽的style屬性只保留src的正則函數(shù)9. ASP實(shí)現(xiàn)加法驗(yàn)證碼10. jsp+servlet實(shí)現(xiàn)猜數(shù)字游戲
