JAVA中的糕富帥技術

前言java

  忽然發現很久沒寫博客了,前面寫的都是關於Android的東西,今天心血來潮忽然有一種衝動想寫一篇基於JAVA技術的博客,別問我爲何?有錢、任性!框架

  今天就來談談反射機制;學過JAVA的人不必定懂得反射,可是必定據說過反射,不過也僅僅是據說過而已;由於反射用的地方也不會那麼多,可是反射用的妙常常會解決咱們撓破頭皮的大問題。至於諸如爲何叫作反射、而不叫作正射倒射此類的歷史問題,仍是交給歷史學家去研究吧。。。this

反射的基石.net

  在談反射以前,咱們應該先了解下類的概念來引入。類是一種抽象的概念,舉個例子「我爸是李剛我爸李雙江」,從這句話中咱們發現有李剛、李雙江這兩我的,咱們來抽象它們的特色,咱們發現它們都像人。沒錯,那麼咱們就能夠將人做爲它們的一個抽象,反過來講李剛和李雙江就是人的一個具體實例;因此咱們能夠用一個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);

咱們運行程序,會發現輸出了:get

true
true博客

這三個玩意居然是同一個東西,那麼就很好解釋了:在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: age
at 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()時,並不須要傳入一個對象,固然你非要傳入一個具體的實例也是沒有關係的,答案依然正確。

 

對於反射就先講這麼多吧,後面有時間再來說講反射的深刻應用以及反射在框架搭建的用處。又浪費我一夜遊戲時間了。。。。任性呀。。。

相關文章
相關標籤/搜索