Java反射:框架設計的靈魂

Java 在運行時識別對象和類的信息轿衔,主要有兩種方式:一種是傳統(tǒng)的RTTI(Run-Time Type Identification),它假定在編譯時已經(jīng)知道了所有的類型信息斑匪;另一種是反射機制,它允許在運行時發(fā)現(xiàn)和使用類的信息疤剑。使用的前提條件:必須先得到目標類的字節(jié)碼的 Class蹬癌,Class 類用于表示.class文件(字節(jié)碼)

一、反射的概述

Java 反射機制是在運行狀態(tài)中镀梭,對于任意一個類刀森,都能夠知道這個類的所有屬性和方法;對于任意一個對象报账,都能夠調(diào)用它的任意一個方法和屬性研底。這種動態(tài)獲取信息以及動態(tài)調(diào)用對象的方法的功能稱為 Java 的反射機制。
要想解剖一個類透罢,必須先要獲取到該類的字節(jié)碼文件對象榜晦。而解剖使用的就是 Class 類中的方法。所以先要獲取到每一個字節(jié)碼文件對應的 Class 類型的對象羽圃。反射就是把 Java 類中的各種成分映射成一個個的 Java 對象乾胶。

一個類有:成員變量、方法朽寞、構造方法识窿、包等等信息,利用反射技術可以對一個類進行解剖脑融,把各個組成部分映射成一個個對象(其實:一個類中這些成員方法喻频、構造方法,在加入類中都有一個類來描述)吨掌。如圖是類的正常加載過程:反射的原理在于 class 對象。Class 對象的由來是將 class 文件讀入內(nèi)存脓恕,并為之創(chuàng)建一個 Class 對象膜宋。
類的加載過程

二、反射的理解

什么是反射炼幔?反射就是在運行時才知道要操作的類是什么秋茫,并且可以在運行時獲取類的完整構造,并調(diào)用對應的方法乃秀。
一般肛著,使用某個類時必定知道它是什么類圆兵,是用來做什么的。于是直接對這個類進行實例化枢贿,之后使用這個類對象進行操作殉农。如:

Phone phone = new Phone(); //直接初始化「正射」
phone.setPrice(4);

如此進行類對象的初始化,可以理解為「正」局荚。而反射則是一開始并不知道要初始化的類對象是什么超凳,自然也無法使用 new 關鍵字來創(chuàng)建對象了。這時候耀态,使用 JDK 提供的反射 API 進行反射調(diào)用:

Class clz = Class.forName("com.xxp.reflect.Phone");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上面兩段代碼的執(zhí)行結果完全一樣轮傍。但是思路完全不一樣,第一段代碼在未運行時就已經(jīng)確定了要運行的類(Phone)首装,而第二段代碼則是在運行時通過字符串值才得知要運行的類(com.xxp.reflect.Phone)创夜。完整代碼如下:

public class Phone {
    private int price;
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的調(diào)用
        Phone phone = new Phone();
        phone.setPrice(5000);
        System.out.println("Phone Price:" + phone.getPrice());
        //使用反射調(diào)用
        Class clz = Class.forName("com.xxp.api.Phone");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor phoneConstructor = clz.getConstructor();
        Object phoneObj = phoneConstructor.newInstance();
        setPriceMethod.invoke(phoneObj, 6000);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Phone Price:" + getPriceMethod.invoke(phoneObj));
    }
}

代碼中使用反射調(diào)用了 setPrice(),并傳遞了 6000 的值仙逻。之后使用反射調(diào)用了 getPrice()驰吓,輸出其價格。輸出結果:

Phone Price:5000
Phone Price:6000

小結:一般使用反射獲取一個對象的步驟:

//獲取類的 Class 對象實例
Class clz = Class.forName("com.xxp.api.Phone");
//根據(jù) Class 對象實例獲取 Constructor 對象
Constructor phoneConstructor = clz.getConstructor();
//使用 Constructor 對象的 newInstance() 獲取反射類對象
Object phoneObj = phoneConstructor.newInstance();

如果要調(diào)用某一個方法桨醋,則需要經(jīng)過下面的步驟:

//獲取方法的 Method 對象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
//利用 invoke 調(diào)用方法
setPriceMethod.invoke(phoneObj, 6000);

三棚瘟、反射的常用API

在 JDK 中,反射相關的 API 可分為:獲取反射的 Class 對象喜最、通過反射創(chuàng)建類對象偎蘸、通過反射獲取類屬性方法及構造器。反射常用 API:

1??獲取反射中的 Class 對象
在反射中瞬内,要獲取一個類或調(diào)用一個類的方法迷雪,首先需要獲取到該類的 Class 對象。在 Java API 中虫蝶,獲取 Class 類對象有三種方法:

  1. 【調(diào)用 Class 的 Class.forName 靜態(tài)方法】當知道某類的全路徑名時章咧,可以使用此方法獲取 Class 類對象。用的最多能真,但可能拋出 ClassNotFoundException 異常赁严。
Class c1 = Class.forName(“java.lang.String”);
  1. 【調(diào)用運行時類的屬性類名.class】該方法最為安全可靠,程序性能更高粉铐。這說明任何一個類都有一個隱含的靜態(tài)成員變量 class疼约。這種方法只適合在編譯前就知道操作的 Class。
Class c2 = String.class;
  1. 【通過運行時類的對象調(diào)用 getClass()】通常應用在:比如傳過來一個 Object 類型的對象蝙泼,而不知道具體是什么類程剥,用這種方法。
String str = new String("Hello");
Class c3 = str.getClass();
  1. 【使用類的加載器 ClassLoader】(了解)
ClassLoader classLoader = Phone.class.getClassLoader();
Class c4 = classLoader.loadClass("com.xxp.api.Phone");

注意:一個類在 JVM 中只會有一個 Class 實例汤踏,即對上面獲取的c1织鲸、c2舔腾、c3和c4進行 equals 或者雙等于比較,發(fā)現(xiàn)都是 true搂擦。是單例的稳诚。

2??通過反射創(chuàng)建類對象
通過反射創(chuàng)建類對象主要有兩種方式:通過 Class 對象的 newInstance()、通過 Constructor 對象的 newInstance()盾饮。

  1. 通過 Class 對象的 newInstance()采桃。
Class clz = Phone.class;
Phone phone = (Phone)clz.newInstance();
  1. 通過 Constructor 對象的 newInstance()
Class clz = Phone.class;
Constructor constructor = clz.getConstructor();
Phone phone= (Phone)constructor.newInstance();

通過 Constructor 對象創(chuàng)建類對象可以選擇特定構造方法,而通過 Class 對象則只能使用默認的無參數(shù)構造方法丘损。下面的代碼就調(diào)用了一個有參數(shù)的構造方法進行了類對象的初始化普办。

Class clz = Phone.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Phone phone = (Phone)constructor.newInstance("華為",6666);

3??通過反射獲取類屬性、方法徘钥、構造器
通過 Class 對象的 getFields() 可以獲取 Class 類的屬性衔蹲,但無法獲取私有屬性。

Class clz = Phone.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

輸出結果是:
price
而如果使用 Class 對象的 getDeclaredFields() 則可以獲取包括私有屬性在內(nèi)的所有屬性:

Class clz = Phone.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

輸出結果是:

name
price

與獲取類屬性一樣呈础,當獲取類方法舆驶、類構造器時,如果要獲取私有方法或私有構造器而钞,則必須使用有 declared 關鍵字的方法沙廉。

附:查閱 API 可以看到 Class 有很多方法:
  getName():獲得類的完整名字。
  getFields():獲得類的public類型的屬性臼节。
  getDeclaredFields():獲得類的所有屬性撬陵。包括private 聲明的和繼承類
  getMethods():獲得類的public類型的方法鲫竞。
  getDeclaredMethods():獲得類的所有方法擒权。包括private 聲明的和繼承類
  getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數(shù)指定方法的名字培他,parameterTypes 參數(shù)指定方法的參數(shù)類型粉臊。
  getConstructors():獲得類的public類型的構造方法草添。
  getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes 參數(shù)指定構造方法的參數(shù)類型扼仲。
  newInstance():通過類的不帶參數(shù)的構造方法創(chuàng)建這個類的一個對象远寸。

四、反射源碼解析

在 Web 項目中偶爾會遇到下面的異常:

java.lang.NullPointerException 
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:369)

可以看到異常堆棧指出了異常在 Method 的第 369 的 invoke 方法中屠凶,其實這里指的 invoke 方法就是反射調(diào)用方法中的 invoke驰后。

Method method = clz.getMethod("setPrice", int.class); 
method.invoke(object, 6);   //就是這里的invoke方法

例如經(jīng)常使用的 Spring 配置中,經(jīng)常會有相關 Bean 的配置

<bean class="com.xxp.Phone"></bean>

當在 XML 文件中配置了上面這段配置之后阅畴,Spring 便會在啟動的時候利用反射去加載對應的 Phone類倡怎。而當 Apple 類不存在或發(fā)生啟發(fā)異常時迅耘,異常堆棧便會將異常指向調(diào)用的 invoke 方法贱枣。由此可知监署,很多框架都使用了反射,而反射中最重要的就是 Method 類的 invoke 方法了纽哥。

五钠乏、反射總結

反射機制允許程序在運行時取得任何一個已知名稱的 class 的內(nèi)部信息,包括包括其 modifiers(修飾符)春塌,fields(屬性)晓避,methods(方法)等,并可于運行時改變 fields 內(nèi)容或調(diào)用 methods只壳。由此便可以更靈活的編寫代碼俏拱,代碼可以在運行時裝配,無需在組件之間進行源代碼鏈接吼句,降低代碼的耦合度锅必;還有動態(tài)代理的實現(xiàn)JDBC原生代碼注冊驅(qū)動惕艳;hibernate 的實體類搞隐;Spring的AOP 等等。但是凡事都有兩面性远搪,反射使用不當會造成很高的資源消耗劣纲。

六、new對象和反射得到對象的區(qū)別

  1. 在使用反射的時候谁鳍,必須確保這個類已經(jīng)加載并已經(jīng)連接了癞季。使用 new 的時候,這個類可以沒有被加載棠耕,也可以已經(jīng)被加載余佛。
  2. new 關鍵字可以調(diào)用任何 public 構造方法。反射可以調(diào)用特定構造方法窍荧,甚至私有方法辉巡。
  3. new 關鍵字是強類型的,效率相對較高蕊退。 反射是弱類型的郊楣,效率低。
  4. 反射提供了一種更加靈活的方式創(chuàng)建對象瓤荔,得到對象的信息净蚤。如 Spring 中 AOP 等的使用,動態(tài)代理的使用输硝,都是基于反射的今瀑。解耦。

七、哪些類型可以有Class對象橘荠?

// 哪些類型可以有Class對象屿附?
// 所有類型的Class
public class ClassDemo {
    public static void main(String[] args) {
        Class c1 = Object.class;        //類
        Class c2 = Comparable.class;    //接口
        Class c3 = String[].class;      //一維數(shù)組
        Class c4 = int[][].class;       //二維數(shù)組
        Class c5 = Override.class;      //注解
        Class c6 = ElementType.class;   //枚舉
        Class c7 = Integer.class;       //基本數(shù)據(jù)類型
        Class c8 = void.class;          //空類型
        Class c9 = Class.class;         // Class本身
        
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);
        
        //只要元素類型與維度一樣,就是同一個Class
        //同一個元素同一個類只有一個Class對象
        //一個類只有一個Class對象
        int[] a = new int[10];
        int[] b = new int[100];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
    }
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哥童,一起剝皮案震驚了整個濱河市挺份,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贮懈,老刑警劉巖匀泊,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異朵你,居然都是意外死亡各聘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門抡医,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伦吠,“玉大人,你說我怎么就攤上這事魂拦∶牵” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵芯勘,是天一觀的道長箱靴。 經(jīng)常有香客問我,道長荷愕,這世上最難降的妖魔是什么衡怀? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮安疗,結果婚禮上抛杨,老公的妹妹穿的比我還像新娘。我一直安慰自己荐类,他們只是感情好怖现,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著玉罐,像睡著了一般屈嗤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吊输,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天饶号,我揣著相機與錄音,去河邊找鬼季蚂。 笑死茫船,一個胖子當著我的面吹牛琅束,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播算谈,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼狰闪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了濒生?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤幔欧,失蹤者是張志新(化名)和其女友劉穎罪治,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體礁蔗,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡觉义,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了浴井。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晒骇。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖磺浙,靈堂內(nèi)的尸體忽然破棺而出洪囤,到底是詐尸還是另有隱情,我是刑警寧澤撕氧,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布瘤缩,位于F島的核電站,受9級特大地震影響伦泥,放射性物質(zhì)發(fā)生泄漏剥啤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一不脯、第九天 我趴在偏房一處隱蔽的房頂上張望府怯。 院中可真熱鬧,春花似錦防楷、人聲如沸牺丙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赘被。三九已至,卻和暖如春肖揣,著一層夾襖步出監(jiān)牢的瞬間民假,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工龙优, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羊异,地道東北人事秀。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像野舶,于是被迫代替她去往敵國和親易迹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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