反射機制 小小談

反射機制(Reflection)

寫在前面

本文地址:http://www.javashuo.com/article/p-mpsxbqsx-kh.html
這裏是Oberon
本文容許轉載,但請註明出處!若有轉載,請通知做者本人
不反對您轉載,可是你說把我原來的格式都轉沒了,我這文風再玩點梗啥的,你這一轉載把你們帶溝裏你說這算你責任算我責任呢。
至少博客園裏能保證一個完整的格式,能讓讀者知道我哪句話在扯皮,那句話在說正經事
並且不建議輕易地轉載個人這篇文章,我對於我寫的東西也沒有十足的把握保證足夠嚴謹,都是我本身的我的理解。
若是您沒有通知我就轉載並被我發現了並且還轉的一塌糊塗,我會把您地址附到這裏,就像這樣html

https://www.h3399.cn/201906/701780.html數據庫

以後還請您閱讀愉快編輯器

何爲反射

反射是在兩種物質分界面上改變傳播方向返回原來物質中的現象
反射是生物體對外界刺激作出應激行爲的過程,根據產生的緣由分爲條件反射非條件反射等,典型的實驗案例包括巴甫洛夫的狗……
反射是一些面向對象程序設計語言提供的針對對象元數據(Metadata)的一種訪問機制函數

元……數據??什麼高深莫測的武功??

啊,誠然,一旦涉及到「元XXX」事情一般就開始變得無比抽象,以致於我不由唸叨起那句訣flex

太極生兩儀,兩儀生四象,四象生八卦……ui

不過元數據這個概念在數據庫裏仍是比較常見的,好比,某個關係型數據庫裏有張表:加密

水果設計

編號 名字 數量
1 蘋果 6
2 香蕉 3
3 5
4 橘子 3
5 菠蘿 2

數據,就是存在表裏的一條一條的記錄,(1,蘋果,6),(3,梨,5)都是數據,那麼,元數據就是凌駕於這些數據之上的用於描述數據數據,對於這張表而言,也就是這張表的表頭(關係數據理論裏稱之爲關係模式):(編號,名稱,數據)指針

劃重點
元數據(Metadata):用於描述數據的數據code

好像有些明朗了,但那關面向對象什麼事呢

衆所周知,類(Class)是面向對象的一個重要概念,儘管,針對於數據庫來講,對象模型和關係模型是不一樣的概念(上文提到的是關係模型的一個例子),可是,對象模型中的對象和關係模型中的關係,其級別是等同的。

關係……又對象……愈來愈聽不懂了

好吧,咱們先把關係放在一邊,咱們只把上邊的東西看作一張表。

難道你就沒有把它改寫成以下形式的衝動嗎??

public class Fruit
{
    public int no;
    public string name;
    public int count;
    
    public Fruit(int no, string name, int count)
    {
        // ...
    }
}

好了,上面的類定義的語義就是

有這樣一類東西,咱們稱呼這類東西爲水果,結構以下……

那麼,這樣一來,咱們就能夠定義一個no爲9,name叫作「西瓜」,count爲5的一個對象,這個對象具備具體的數據。

而上面的類定義代碼,包含的就是這個類的元數據

說的再直白點吧

以人爲例,數據注重的是這人的臉長啥樣,而元數據注重的是這人有沒有臉(好像不太對……)

好吧差很少了解了,但元數據和反射有什麼關係呢

反射是一些面向對象程序設計語言提供的針對對象元數據(Metadata)的一種訪問機制

本文一開始就說了,罰站20年

不過在此以前先解釋一件事,元數據在哪

任何一個面向對象的程序設計語言,其類類型都具有一個元數據的存儲,至少程序會使用這個元數據可以動態地構造此類的對象。但不一樣的語言機制不一樣,好比C++這種的,由於直接和系統進行愉♂快的互♂動,所以元數據就直接使用系統的內存地址了,這種數據使用是很不直觀的,同時也不使用任何託管機制作後援(巨硬魔改的C++/CLI不在討論範圍內),所以這種貼近底層的語言不支持反射機制,雖然能夠經過強行向程序代碼中經過工廠類模式強行注入可讀的元信息(方法參見這位大佬的文章)。

可是,正如前面所說的,若是元數據在託管編譯或解釋的狀態下會保留一份可讀的版本,這是提供給解釋器或者託管平臺用的,固然,這種狀況下語言通常會提供一個較爲完善的元數據訪問機制,這就是反射。這類語言典型的表明就是C#(.NET託管)、Java(JVM虛擬機)、Python(解釋器提供)等。

那……反射是如何運做的呢??

反射嘛。那還不容易,拿個鏡子就能夠了呀!
或者用羊角錘偷襲的方式砸膝蓋什麼的也是很容易的呀!
不過這麼說來,拿羊角錘偷襲鏡子豈不是更棒!!

正如以前所說,反射機制是對類的元數據的獲取和操縱,所以,一個重要的前提就是:

這個程序設計語言的運做機制當中,類的元數據必須是可見的,若是可讀的話那更好

只有當類的元數據是可見的,反射機制纔有訪問它們的可能,可是元數據的可讀性會決定反射機制訪問它們的難易程度。

這裏補充一句,有人會說,在使用IDE或者代碼編輯器的時候,咱們寫object.property這種訪問方式的時候編譯器不就直接告訴咱們了麼??
關於這一點,這裏暫時只說一個前提:

反射機制的實際動做是聚焦於運行時(Runtime)的。

在程序代碼編譯以前咱們恣意地書寫這MyObject.id.hashCode.getFlush().balabala的時候,這是預編譯的過程,預編譯的時候固然這些元數據都是以字面形式給出的(由於你的代碼裏寫了這個類的定義),你能夠很是愉悅地Ctrl+C Ctrl+V或者享受着IntelliSense帶給你的N倍快樂,這個時候再談反射就沒什麼意義了,所以,反射機制訪問元數據都是在編譯後運行時發生的。

明明都是面向對象,爲何恰恰C++不支持這個東西呢

以C++爲例,這些元數據是否可見?答案是確定的,那爲何不支持反射機制呢,由於這些元數據是以指針的方式給出的,指針在已編譯的C++程序中的存在形式就是地址,說的再粗暴點,就是4或8字節的二進制數……
也就是說,在已經編譯完成的C++程序的眼裏,類的元數據已經變成二進制的地址碼了,若是某人在沒有源代碼的狀況下想給這個項目寫一個反射機制,那麼他將不得不面對一大堆的:

0xb08dfe231a1c002e
0xb08dfe231bc128f6
0xb08dfe2417a90f5d
......

看到這些,他長舒了一口氣,優雅地點燃了一根香菸,而後絕不猶豫地戳到電腦屏幕上:

鬼知道這是什麼玩意啊!!

若是原項目加個殼、模板元編一下再作個混淆加密的話那更無法看了,所以若是必定要實現反射機制,通常都是把反射機制直接囊括到項目開發過程中(就像上面那位大佬的文章中提到的那樣,原項目的做者也是反射機制的構造者)。
這樣的話就會存在一個上上上個世紀汽車行業出現的問題:

這輛車的件沒法用到另一輛車上!
這個反射機制沒法用到別的項目上!

固然,這樣說可能有些絕對,但以C++的方式實現一個可以普遍用於全部項目的反射機制應該是極端困難的。
上面大佬的文章當中,這個C++的項目要使用反射機制,是藉助工廠模式實現的,關於這些的實現方法,詳見大佬文章(固然我本身也沒徹底看懂)

那託管語言又如何呢

C#、Java,這兩種語言都是託管代碼的(C#使用.NET進行託管,Java則交給了JVM虛擬機)。

與C++不一樣的是,他們並不直接接觸系統底層,而是經過中間代碼訪問底層的。

中間代碼由誰處理呢,C#是經過.NET提供的CLR,產生的中間語言是程序集,而Java靠的是JVM,其中間產物是class文件。
若是有幸使用一些IDE打開這兩個文件往裏窺探一遭的話,咱們應當不難從中找到這些元數據的信息。

這就好像,一羣孩子進了幼兒園,一個託管老師全程進行看護。

把拔碼麻區上辦,我區悠貳園吶

固然,託管老師確定是知道孩子叫什麼名的,訪問他們天然也是很容易的。同理,託管環境(或虛擬環境)也是同樣的,由於銜接上下兩層,所以把底層的元數據和上層的可讀文本構造反射的橋樑是很容易辦到的,所以,C#和Java都提供了一套很是完善的反射庫,他們能夠被用於使用這兩種語言寫的任意一個類當中。

好了,道理我都懂,但爲何要反射呢?

反射能幹什麼呢

舉個最簡單的例子

我……我有一個夢想,我想要這樣一個函數,可以返回Person類是否有我所說的方法,可是我不知道Person類裏有什麼,好比我想問他有沒有Eat()方法,它返回true,我問他有沒有Fly()方法,它能返回false

好了,換做是你,你會怎麼實現這樣一個函數呢??

而反射機制偏偏作到了!
你提供給反射機制一個字符串形式的函數名,反射機制不只能夠得知這個函數是否存在,甚至能幫助你去執行這個函數(Invoke)。

什麼,你很差問它有沒有某個函數??好啊,反射機制甚至能夠告訴你這個類都有哪些屬性哪些函數,繼承自誰,可見性如何,是否抽象等等。

那反射在何時比較好用呢

上面那個例子其實就是一個經典的用途。

或者,咱們能夠考慮另一個場景。

你寫了某個函數接受了一個抽象爲Object的對象,你但願,若是Object的對象存在方法Grow則調用之,不然什麼也不作。

這個時候首先能夠經過反射機制肯定方法是否存在,但即使方法已經存在,咱們是沒法直接調用的,由於對象已經抽象爲Object,而Object並不存在方法Grow,因此直接調用就洗洗睡了。

咱們不能具象回來麼??

若是咱們知道類在抽象以前是什麼類型的時候,那固然能夠具象化回來。
可是抽象雖然發生於編譯時或運行時(動態建立的對象),但具象類型的獲知倒是在編譯以前的代碼源文件,並且還有些時候你根本沒法知道原類型,那也沒辦法拆箱。


這裏面我爲了方便,也是想不出啥更好的詞
這裏我稱派生類基類的多態轉化爲抽象
反過來的過程稱爲具象

那我還怎麼調用Grow

反射機制能夠獲取到完整的可用方法的列表,咱們在列表中找到了Grow,存在形式爲Method/MethodInfo對象或乾脆就是個字符串。

但不管是哪一種,obj.Grow();是不可能了,好在反射機制連這件事都考慮在內了——Invoke調用!!

反射機制不只知道你想要什麼方法,還能夠幫助你調用這個方法,這個調用就經過一個叫作Invoke的方法完成。

不一樣語言對Invoke的定義不盡相同但功能上大同小異,經過Invoke調用某方法的過程實質上是轉調回調(或者是間接調用)。
間接調用比直接調用更加的強大靈活,但繞了遠路。

還有什麼比較宏大一點的應用麼??

宏大一點……好吧,其實每個磅礴的工程都是從一點一滴作起的。

一個很經典的案例,就是上文那篇大佬文章裏的一個經常使用功能——序列化(Serialization)
雖然C#和Java自己就有能夠用於序列化的一些結構和功能庫(Serializable接口之類的),可是有些時候咱們對序列化機制若是有更高的可定製性要求的時候,咱們每每傾向於本身構建一套心儀的序列化功能庫。

因而乎就有一個最簡單的問題擺在面前:

如今有Class1類的對象,還有Class2類的對象,還有Class3類的一些對象想要轉化成可解析的內容,以供發送或保存(固然這也就意味着,這些對象的全部屬性和狀態都要保存),可是這老大老二老三一家一個樣,屬性也各不相同,我又不想挨個單獨寫,那該怎麼辦呢??

如今有了反射機制,問題就很容易解決了。三胞胎嫌分起來麻煩??反射機制能夠把他們安排的明明白白!!你能夠向反射類提供一個完整的類名,反射機制就能保證給你這個類對應的可用屬性的列表,以及一整套處理方案(Get和Set),以後還不是想來啥來啥,美滋滋~~

固然,以上都是反射機制用途中小的不能再小的冰山一角,好比我還能夠經過反射機制根據個人輸入建立我想要的類型的對象等等。

哇,反射這麼強大??我要滿地反射!!

冷靜點!任何事物都有多面性,反射也不例外,咱們看看反射機制有什麼特色,它究竟是否適合全部情形。

極致靈動(Flexi Frenzy) 稀有屬性

反射機制可讓你的代碼很是靈活,以不變應萬變。
這也正是反射機制帶來的最大的好處。

未卜先知(Fortune Tell) 普通屬性

反射機制是在運行時起做用,固然,運行期間發生什麼,編譯以前是沒法獲知的,反射就是處理這件事的。

效率捉急(Emaciated Efficiency) 糟糕屬性

反射機制最大的問題!

反射機制的效率是十分低下的,首先在運行時獲取元數據再轉化成可讀形式就不是一個很快的過程,而反射的Invoke調用是個徹徹底底的間接調用。

不當地使用大量反射會致使程序效率的急劇降低。

代碼膨脹(Code Expansion)

顯然,用反射進行調用的代碼每每比直接調用寫起來複雜,因此除非你寫代碼是按行數計工資,不然能直接調用就不要反射。

健壯風險(Robustness Risk)

反射機制通常容許用戶傳入字符串……

而後就是萬劫不復深淵之伊始

這時候用戶傳的字符串就能夠很是的五花八門了,就好像一個動物園裏,反射機制是一個可愛的小動物,而遊客開始不分青紅皁白地對它投各類食,參差不齊,但是你的反射機制很脆弱,它可禁不起這折騰,吃到很差的東西就會生病罷工(拋異常,而後停止),所以你這當奶媽奶爸就要多操心,幫它收拾(捕獲),告訴他如何分辨食物(預先判斷)……

不過呢,有些時候引入反射機制偏偏就是出於健壯性的考慮……

若是我養的不是個反射機制而是一隻熊貓的話我會上天的!!

總結

反射是個強大的武器,但使用應多加謹慎!

以上

相關文章
相關標籤/搜索