JAVA中的糕富帥技術——反射(一)
今天就來談談反射機制;學過JAVA的人不一定懂得反射,但是一定聽說過反射,不過也僅僅是聽說過而已;因為反射用的地方也不會那么多,但是反射用的妙經常會解決我們撓破頭皮的大問題。至于諸如為什么叫做反射、而不叫做正射倒射此類的歷史問題,還是交給歷史學家去研究吧。。。
反射的基石
在談反射之前,我們應該先了解下類的概念來引入。類是一種抽象的概念,舉個例子“我爸是李剛我爸李雙江”,從這句話中我們發現有李剛、李雙江這兩個人,我們來抽象它們的特點,我們發現它們都像人。沒錯,那么我們就可以將人作為它們的一個抽象,反過來說李剛和李雙江就是人的一個具體實例;所以我們可以用一個Person類代表人來表示這種抽象。既然理解了類的概念,那些年那些陪我們度過日日夜夜的java類們,我們是不是也應該抽象出一個類來證明一下他們,沒錯,那就是Class了!
Class就是java類的抽象,它抽象出了java的共性,如類的名字、類的構造方法、類的成員變量、類的老爸、類的方法等等等。既然這么說,那么我們通過這個Class,我們就可以得到這個類的方方面面的信息、興許還能比查戶口還詳細呢。我們創建出的每一個類,例如person類,說到底也就是我們實例化了一個Class的實例,來保存person類的名字、變量、方法這些信息,在內存中表示就是保存了person類的字節碼,如果你理解了這些并且接受了我的看法,那么咱們有共同語言,可以繼續往下說。
既然person類有擁有自己的字節碼,那么我們可以獲取到這個字節碼嗎?答案是肯定的,而且還不止一種方法。參見代碼:
public static void main(String[] args) throws Exception {//第一種方法,直接通過Person類來獲取字節碼 Class cls1 = Person.class;//第二種方法,通過類的實例來獲取Person類的字節碼 Person person = new Person();Class cls2 = person.getClass();//第三種方法,調用Class類的靜態方法來獲取對應類的字節碼,該方法會拋出異常 Class cls3 = Class.forName('Person');}
從代碼中看,我們可以斷定:Person類的字節碼就是Class的具體實例;我們也可以猜到,至于類的字節碼有包含什么東東,大家盡管猜吧,后面我會慢慢講解。我們再來看看下面的代碼:
System.out.println(cls1 == cls2);System.out.println(cls2 == cls3);
我們運行程序,會發現輸出了:
truetrue
這三個玩意竟然是同一個東西,那么就很好解釋了:在java的虛擬機中,每一個類都會被保存成為一個字節碼,用來保存該類的信息如名字、父類、變量、方法等。一個類的字節碼在虛擬機中有且只有一個,也就是在第一次加載該類的時候會將類的字節碼加載到java虛擬機中,而上面有三種方法可以從虛擬機中獲取類的字節碼(PS:第三種方法最為常用),但是你別疑惑獲取這個字節碼干嘛嘛用,我們要反射嘛,說白了我們就是要來強暴這字節碼(Class)。。。。。(~ o ~)~zZ
理解反射
既然前面講解了Class類,現在我們可以開始講反射了。反射是什么呢?反射就是將類的各種成分映射成各種類,我們知道一個java類可以用一個class的對象來表示,這個類的組成成分有名字、變量、構造方法等信息,我們當然可以用一個個java類的表示。換句話說,表示java類的Class類提供了一系列的方法給我們用來獲取其中的變量、方法、構造方法等信息,這些信息也有相應的類的實例來表示,也就是Field、Method、Constructor等等。或者更通俗的說,Field就是java類中的所有變量的抽象、同理Method就是java類中所有方法的抽象,如果還是看不懂,很正常,往下看代碼估計更好理解。
構造方法的反射
從前面我們知道,Constructor就是java類所有構造方法的抽象。那么我們怎么通過反射來獲取類的構造方法呢,參見代碼:
public class Test{ public static void main(String[] args) throws Exception {Class cls = Person.class;//獲取Person類的字節碼 Constructor constructor1 = cls.getConstructor();//調用getConstructor()獲取Person無參構造方法 Person p1 = (Person) constructor1.newInstance();//通過調用newInstance()來執行無參構造方法 Constructor constructor2 = cls.getConstructor(int.class);//調用getConstructor(*.class)獲取Person帶參構造方法 Person p2 = (Person) constructor2.newInstance(1);//通過調用newInstance(int)來執行帶參構造方法 }} class Person{ public Person(){System.out.println('無參構造方法');} public Person(int i){System.out.println('帶參構造方法');}}
控制臺輸出:
無參構造方法帶參構造方法
這里我們開始講解一下,代碼通過Person.class來獲取Person類的字節碼并將其保存在一個Class類的實例cls中,然后再通過cls.getConstructor()來獲取字節碼中的構造方法并將其放入Constructor的實例constructor之中,很明顯,這個constructor并不是Person的構造方法,而是保存Person構造方法的一個實例,所以我們可以通過調用newInstance()來獲取保存在constructor中的person類的構造方法并執行,構造方法執行并返回一個Object的實例,并將其強轉為Person并保存在person的變量中,這就是調用反射來獲取構造方法生成實例的全過程。
在代碼中,我們也可以知道怎么獲取帶參的構造方法,這是我們需要在getConstructor()是傳入構造方法對應參數的字節碼,例如代碼中Person(int i)我們需要傳入一個int.class(或者是Integet.TYPE)的字節碼提供給Class定位需要獲取的構造方法。但是如果你比較貪心想獲取全部的構造方法,沒問題,通過getConstructors():
Class cls = Person.class;//獲取Person類的字節碼 Constructor[] constructors = cls.getConstructors();//調用getConstructor()獲取Person無參構造方法 for(Constructor c : constructors){//Person p = c.newInstance(****);遍歷執行構造方法 }
然后通過for循環,就可以處理你所需要的構造方法了。
成員變量的反射
我們說完了構造方法的反射,我們就接下來談談成員變量的反射的用法。慣例還是先看代碼:
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的這個變量,并沒有對應到對象身上 // System.out.println(fieldNmae); //fieldNmae不代表具體的值,只代表一個變量,所以我們需要傳入一個person實例才能獲取到其對應的值 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;//對于某些人來說,年齡是秘密! }
跟構造方法的反射的實現差不多,我們也是先通過獲取Person的字節碼cls,然后從其中將Person的成員變量映射成一個Field類,在這里我們將Person.name這個變量映射成fieldName這個對象,當然我們不可能單純的從fieldName這個對象中獲取咱們的“小紅”,因為fieldName是從cls中獲取的而并不是從person的實例中獲取的,所以它值并不是小紅;而是我們可以通過小紅這個person的實例p與fieldName聯系起來,也就是調用fieldName.get(p)才能獲取小紅這個字符串。
但是我們如果想獲取小紅年齡呢,女人的年齡大多是秘密,私有變量我們也可以這樣獲取嗎?修改下代碼:
Person p = new Person('小紅', 20);Class cls = Class.forName('com.net168.test.Person');Field fieldAge = cls.getField('age');System.out.println(fieldAge.get(p));
執行結果是:
Exception in thread 'main' java.lang.NoSuchFieldException: ageat java.lang.Class.getField(Unknown Source)at com.net168.test.Test.main(Test.java:11)
沒有這個字段,明明是有這個age的字段呀!但是我們發現,原來這個女生的年齡是私有的,她就是不肯告訴咱們啊,那怎么辦?她不想告訴我們,我們就沒法知道了嗎?屌絲是不會那么容易屈服的!所以我們可以稍作一點處理,如下:
Person p = new Person('小紅', 20);Class cls = Class.forName('com.net168.test.Person');Field fieldAge = cls.getDeclaredField('age');//獲取類的私有變量 fieldAge.setAccessible(true);//設置該私有變量可被外面訪問 System.out.println(fieldAge.get(p));
可以通過getDeclaredField()來獲取Person類的私有變量,而且我們還可以在獲取到外界看不到的私有變量后,再通過setAccessible(true)設置該私有變量可以被強制訪問。暴力吧,JAVA的反射也被有些人叫做暴力反射。。。運行代碼,我們就知道了小紅的芳齡 了:20
成員方法的反射
如果大家看懂了前面成員變量和構造方法的反射,基本上再了解成員方法的反射就沒有什么困難了,不賣關子,還是先上下代碼:
public class Test{ public static void main(String[] args) throws Exception {Person p = new Person();Class cls = p.getClass();//獲取Person的字節碼 //獲取setName()方法,需要傳入參數為String Method method1 = cls.getMethod('setName', String.class);method1.invoke(p, '小明');//關聯p,輸入“小明”并執行該方法 //獲取getName()方法,無參則設為null Method method2 = cls.getMethod('getName', null);String name = (String) method2.invoke(p, null);//invoke返回的類型為Object System.out.println(name); //獲取靜態方法,由于靜態方法只依賴與類,所以不需要提供具體的實例 Method method3 = cls.getMethod('show', int.class); // method3.invoke(p, 1);提供具體實例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('設置name值為:' + name); } public static void show(int i){System.out.println('這是一個靜態方法:' + i); }}
程序運行結果:
設置name值為:小明小明這是一個靜態方法:1
在方法的反射中,我們是利用了Method這個類,由于跟構造方法類似,所以我不就再就獲取有參無參的方法的不同之處進行講解。總體來說,就是通過Person的字節碼獲取到Person類中對應的方法并將其保存到Method的一個對象中,然后通過這個對象跟Person的具體實例進行搭配,通過invoke()就可以調用到具體實例的對應方法。在這里我們需要注意的時靜態方法的反射,由于靜態方法屬于一個類并不是屬于特定的一個對象,所以我們在調用靜態方法的invoke()時,并不需要傳入一個對象,當然你非要傳入一個具體的實例也是沒有關系的,答案依然正確。
相關文章:
