運行期類型鑒定_第1頁
運行期類型鑒定_第2頁
運行期類型鑒定_第3頁
運行期類型鑒定_第4頁
運行期類型鑒定_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、第11章 運行期類型鑒定運行期類型鑒定(RTTI)的概念初看非常簡單手上只有基礎類型的一個句柄時,利用它判斷一個對象的正確類型。然而,對RTTI的需要暴露出了面向對象設計許多有趣(而且經常是令人困惑的)的問題,并把程序的構造問題正式擺上了桌面。本章將討論如何利用Java在運行期間查找對象和類信息。這主要采取兩種形式:一種是“傳統(tǒng)”RTTI,它假定我們已在編譯和運行期擁有所有類型;另一種是Java1.1特有的“反射”機制,利用它可在運行期獨立查找類信息。首先討論“傳統(tǒng)”的RTTI,再討論反射問題。11.1 對RTTI的需要請考慮下面這個熟悉的類結構例子,它利用了多形性。常規(guī)類型是Shape類,而

2、特別衍生出來的類型是Circle,Square和Triangle。516頁圖這是一個典型的類結構示意圖,基礎類位于頂部,衍生類向下延展。面向對象編程的基本目標是用大量代碼控制基礎類型(這里是Shape)的句柄,所以假如決定添加一個新類(比如Rhomboid,從Shape衍生),從而對程序進行擴展,那么不會影響到原來的代碼。在這個例子中,Shape接口中的動態(tài)綁定方法是draw(),所以客戶程序員要做的是通過一個普通Shape句柄調用draw()。draw()在所有衍生類里都會被覆蓋。而且由于它是一個動態(tài)綁定方法,所以即使通過一個普通的Shape句柄調用它,也有表現出正確的行為。這正是多形性的作

3、用。所以,我們一般創(chuàng)建一個特定的對象(Circle,Square,或者Triangle),把它上溯造型到一個Shape(忽略對象的特殊類型),以后便在程序的剩余部分使用匿名Shape句柄。作為對多形性和上溯造型的一個簡要回顧,可以象下面這樣為上述例子編碼(若執(zhí)行這個程序時出現困難,請參考第3章小節(jié)“賦值”):516-517頁程序基礎類可編碼成一個interface(接口)、一個abstract(抽象)類或者一個普通類。由于Shape沒有真正的成員(亦即有定義的成員),而且并不在意我們創(chuàng)建了一個純粹的Shape對象,所以最適合和最靈活的表達方式便是用一個接口。而且由于不必設置所有那些abstra

4、ct關鍵字,所以整個代碼也顯得更為清爽。每個衍生類都覆蓋了基礎類draw方法,所以具有不同的行為。在main()中創(chuàng)建了特定類型的Shape,然后將其添加到一個Vector。這里正是上溯造型發(fā)生的地方,因為Vector只容納了對象。由于Java中的所有東西(除基本數據類型外)都是對象,所以Vector也能容納Shape對象。但在上溯造型至Object的過程中,任何特殊的信息都會丟失,其中甚至包括對象是幾何形狀這一事實。對Vector來說,它們只是Object。用nextElement()將一個元素從Vector提取出來的時候,情況變得稍微有些復雜。由于Vector只容納Object,所以nex

5、tElement()會自然地產生一個Object句柄。但我們知道它實際是個Shape句柄,而且希望將Shape消息發(fā)給那個對象。所以需要用傳統(tǒng)的"(Shape)"方式造型成一個Shape。這是RTTI最基本的形式,因為在Java中,所有造型都會在運行期間得到檢查,以確保其正確性。那正是RTTI的意義所在:在運行期,對象的類型會得到鑒定。在目前這種情況下,RTTI造型只實現了一部分:Object造型成Shape,而不是造型成Circle,Square或者Triangle。那是由于我們目前能夠肯定的唯一事實就是Vector里充斥著幾何形狀,而不知它們的具體類別。在編譯期間,我們

6、肯定的依據是我們自己的規(guī)則;而在編譯期間,卻是通過造型來肯定這一點。現在的局面會由多形性控制,而且會為Shape調用適當的方法,以便判斷句柄到底是提供Circle,Square,還是提供給Triangle。而且在一般情況下,必須保證采用多形性方案。因為我們希望自己的代碼盡可能少知道一些與對象的具體類型有關的情況,只將注意力放在某一類對象(這里是Shape)的常規(guī)信息上。只有這樣,我們的代碼才更易實現、理解以及修改。所以說多形性是面向對象程序設計的一個常規(guī)目標。然而,若碰到一個特殊的程序設計問題,只有在知道常規(guī)句柄的確切類型后,才能最容易地解決這個問題,這個時候又該怎么辦呢?舉個例子來說,我們有

7、時候想讓自己的用戶將某一具體類型的幾何形狀(如三角形)全都變成紫色,以便突出顯示它們,并快速找出這一類型的所有形狀。此時便要用到RTTI技術,用它查詢某個Shape句柄引用的準確類型是什么。對象為理解RTTI在Java里如何工作,首先必須了解類型信息在運行期是如何表示的。這時要用到一個名為“Class對象”的特殊形式的對象,其中包含了與類有關的信息(有時也把它叫作“元類”)。事實上,我們要用Class對象創(chuàng)建屬于某個類的全部“常規(guī)”或“普通”對象。對于作為程序一部分的每個類,它們都有一個Class對象。換言之,每次寫一個新類時,同時也會創(chuàng)建一個Class對象(更恰當地說,是保存在一個完全同名的

8、.class文件中)。在運行期,一旦我們想生成那個類的一個對象,用于執(zhí)行程序的Java虛擬機(JVM)首先就會檢查那個類型的Class對象是否已經載入。若尚未載入,JVM就會查找同名的.class文件,并將其載入。所以Java程序啟動時并不是完全載入的,這一點與許多傳統(tǒng)語言都不同。一旦那個類型的Class對象進入內存,就用它創(chuàng)建那一類型的所有對象。若這種說法多少讓你產生了一點兒迷惑,或者并沒有真正理解它,下面這個示范程序或許能提供進一步的幫助:519-520頁程序對每個類來說(Candy,Gum和Cookie),它們都有一個static從句,用于在類首次載入時執(zhí)行。相應的信息會打印出來,告訴我

9、們載入是什么時候進行的。在main()中,對象的創(chuàng)建代碼位于打印語句之間,以便偵測載入時間。特別有趣的一行是:Class.forName("Gum");該方法是Class(即全部Class所從屬的)的一個static成員。而Class對象和其他任何對象都是類似的,所以能夠獲取和控制它的一個句柄(裝載模塊就是干這件事的)。為獲得Class的一個句柄,一個辦法是使用forName()。它的作用是取得包含了目標類文本名字的一個String(注意拼寫和大小寫)。最后返回的是一個Class句柄。該程序在某個JVM中的輸出如下:520頁中程序可以看到,每個Class只有在它需要的時候才

10、會載入,而static初始化工作是在類載入時執(zhí)行的。非常有趣的是,另一個JVM的輸出變成了另一個樣子:520頁下程序看來JVM通過檢查main()中的代碼,已經預測到了對Candy和Cookie的需要,但卻看不到Gum,因為它是通過對forName()的一個調用創(chuàng)建的,而不是通過更典型的new調用。盡管這個JVM也達到了我們希望的效果,因為確實會在我們需要之前載入那些類,但卻不能肯定這兒展示的行為百分之百正確。1. 類標記在Java 1.1中,可以采用第二種方式來產生Class對象的句柄:使用“類標記”。對上述程序來說,看起來就象下面這樣:Gum.class;這樣做不僅更加簡單,而且更安全,因

11、為它會在編譯期間得到檢查。由于它取消了對方法調用的需要,所以執(zhí)行的效率也會更高。類標記不僅可以應用于普通類,也可以應用于接口、數組以及基本數據類型。除此以外,針對每種基本數據類型的封裝器類,它還存在一個名為TYPE的標準字段。TYPE字段的作用是為相關的基本數據類型產生Class對象的一個句柄,如下所示:等價于521頁表格略造型前的檢查迄今為止,我們已知的RTTI形式包括:(1) 經典造型,如"(Shape)",它用RTTI確保造型的正確性,并在遇到一個失敗的造型后產生一個ClassCastException違例。(2) 代表對象類型的Class對象。可查詢Class對象,

12、獲取有用的運行期資料。在C+中,經典的"(Shape)"造型并不執(zhí)行RTTI。它只是簡單地告訴編譯器將對象當作新類型處理。而Java要執(zhí)行類型檢查,這通常叫作“類型安全”的下溯造型。之所以叫“下溯造型”,是由于類分層結構的歷史排列方式造成的。若將一個Circle(圓)造型到一個Shape(幾何形狀),就叫做上溯造型,因為圓只是幾何形狀的一個子集。反之,若將Shape造型至Circle,就叫做下溯造型。然而,盡管我們明確知道Circle也是一個Shape,所以編譯器能夠自動上溯造型,但卻不能保證一個Shape肯定是一個Circle。因此,編譯器不允許自動下溯造型,除非明確指定

13、一次這樣的造型。RTTI在Java中存在三種形式。關鍵字instanceof告訴我們對象是不是一個特定類型的實例(Instance即“實例”)。它會返回一個布爾值,以便以問題的形式使用,就象下面這樣:if(x instanceof Dog)(Dog)x).bark();將x造型至一個Dog前,上面的if語句會檢查對象x是否從屬于Dog類。進行造型前,如果沒有其他信息可以告訴自己對象的類型,那么instanceof的使用是非常重要的否則會得到一個ClassCastException違例。我們最一般的做法是查找一種類型(比如要變成紫色的三角形),但下面這個程序卻演示了如何用instanceof標記

14、出所有對象。522-524頁程序在Java 1.0中,對instanceof有一個比較小的限制:只可將其與一個已命名的類型比較,不能同Class對象作對比。在上述例子中,大家可能覺得將所有那些instanceof表達式寫出來是件很麻煩的事情。實際情況正是這樣。但在Java 1.0中,沒有辦法讓這一工作自動進行不能創(chuàng)建Class的一個Vector,再將其與之比較。大家最終會意識到,如編寫了數量眾多的instanceof表達式,整個設計都可能出現問題。當然,這個例子只是一個構想最好在每個類型里添加一個static數據成員,然后在構建器中令其增值,以便跟蹤計數。編寫程序時,大家可能想象自己擁有類的源

15、碼控制權,能夠自由改動它。但由于實際情況并非總是這樣,所以RTTI顯得特別方便。1. 使用類標記PetCount.java示例可用Java 1.1的類標記重寫一遍。得到的結果顯得更加明確易懂:524-526頁程序在這里,typenames(類型名)數組已被刪除,改為從Class對象里獲取類型名稱。注意為此而額外做的工作:例如,類名不是Getbil,而是,其中已包含了包的名字。也要注意系統(tǒng)是能夠區(qū)分類和接口的。也可以看到,petTypes的創(chuàng)建模塊不需要用一個try塊包圍起來,因為它會在編譯期得到檢查,不會象Class.forName()那樣“擲”出任何違例。Pet動態(tài)創(chuàng)建好以后,可以看到隨機數

16、字已得到了限制,位于1和petTypes.length之間,而且不包括零。那是由于零代表的是Pet.class,而且一個普通的Pet對象可能不會有人感興趣。然而,由于Pet.class是petTypes的一部分,所以所有Pet(寵物)都會算入計數中。2. 動態(tài)的instanceofJava 1.1為Class類添加了isInstance方法。利用它可以動態(tài)調用instanceof運算符。而在Java 1.0中,只能靜態(tài)地調用它(就象前面指出的那樣)。因此,所有那些煩人的instanceof語句都可以從PetCount例子中刪去了。如下所示:526-528頁程序可以看到,Java 1.1的isI

17、nstance()方法已取消了對instanceof表達式的需要。此外,這也意味著一旦要求添加新類型寵物,只需簡單地改變petTypes數組即可;毋需改動程序剩余的部分(但在使用instanceof時卻是必需的)。11.2 RTTI語法Java用Class對象實現自己的RTTI功能即便我們要做的只是象造型那樣的一些工作。Class類也提供了其他大量方式,以方便我們使用RTTI。首先必須獲得指向適當Class對象的的一個句柄。就象前例演示的那樣,一個辦法是用一個字串以及Class.forName()方法。這是非常方便的,因為不需要那種類型的一個對象來獲取Class句柄。然而,對于自己感興趣的類型

18、,如果已有了它的一個對象,那么為了取得Class句柄,可調用屬于Object根類一部分的一個方法:getClass()。它的作用是返回一個特定的Class句柄,用來表示對象的實際類型。Class提供了幾個有趣且較為有用的方法,從下例即可看出:528-529頁程序從中可以看出,class FancyToy相當復雜,因為它從Toy中繼承,并實現了HasBatteries,Waterproof以及ShootsThings的接口。在main()中創(chuàng)建了一個Class句柄,并用位于相應try塊內的forName()初始化成FancyToy。Class.getInterfaces方法會返回Class對象的

19、一個數組,用于表示包含在Class對象內的接口。若有一個Class對象,也可以用getSuperclass()查詢該對象的直接基礎類是什么。當然,這種做會返回一個Class句柄,可用它作進一步的查詢。這意味著在運行期的時候,完全有機會調查到對象的完整層次結構。若從表面看,Class的newInstance()方法似乎是克?。╟lone())一個對象的另一種手段。但兩者是有區(qū)別的。利用newInstance(),我們可在沒有現成對象供“克隆”的情況下新建一個對象。就象上面的程序演示的那樣,當時沒有Toy對象,只有cy即y的Class對象的一個句柄。利用它可以實現“虛擬構建器”。換言之,我們表達:

20、“盡管我不知道你的準確類型是什么,但請你無論如何都正確地創(chuàng)建自己。”在上述例子中,cy只是一個Class句柄,編譯期間并不知道進一步的類型信息。一旦新建了一個實例后,可以得到Object句柄。但那個句柄指向一個Toy對象。當然,如果要將除Object能夠接收的其他任何消息發(fā)出去,首先必須進行一些調查研究,再進行造型。除此以外,用newInstance()創(chuàng)建的類必須有一個默認構建器。沒有辦法用newInstance()創(chuàng)建擁有非默認構建器的對象,所以在Java 1.0中可能存在一些限制。然而,Java 1.1的“反射”API(下一節(jié)討論)卻允許我們動態(tài)地使用類里的任何構建器。程序中的最后一個方

21、法是printInfo(),它取得一個Class句柄,通過getName()獲得它的名字,并用interface()調查它是不是一個接口。該程序的輸出如下:530頁程序所以利用Class對象,我們幾乎能將一個對象的祖宗十八代都調查出來。11.3 反射:運行期類信息如果不知道一個對象的準確類型,RTTI會幫助我們調查。但卻有一個限制:類型必須是在編譯期間已知的,否則就不能用RTTI調查它,進而無法展開下一步的工作。換言之,編譯器必須明確知道RTTI要處理的所有類。從表面看,這似乎并不是一個很大的限制,但假若得到的是一個不在自己程序空間內的對象的句柄,這時又會怎樣呢?事實上,對象的類即使在編譯期間

22、也不可由我們的程序使用。例如,假設我們從磁盤或者網絡獲得一系列字節(jié),而且被告知那些字節(jié)代表一個類。由于編譯器在編譯代碼時并不知道那個類的情況,所以怎樣才能順利地使用這個類呢?在傳統(tǒng)的程序設計環(huán)境中,出現這種情況的概率或許很小。但當我們轉移到一個規(guī)模更大的編程世界中,卻必須對這個問題加以高度重視。第一個要注意的是基于組件的程序設計。在這種環(huán)境下,我們用“快速應用開發(fā)”(RAD)模型來構建程序項目。RAD一般是在應用程序構建工具中內建的。這是編制程序的一種可視途徑(在屏幕上以窗體的形式出現)。可將代表不同組件的圖標拖曳到窗體中。隨后,通過設定這些組件的屬性或者值,進行正確的配置。設計期間的配置要求

23、任何組件都是可以“例示”的(即可以自由獲得它們的實例)。這些組件也要揭示出自己的一部分內容,允許程序員讀取和設置各種值。此外,用于控制GUI事件的組件必須揭示出與相應的方法有關的信息,以便RAD環(huán)境幫助程序員用自己的代碼覆蓋這些由事件驅動的方法?!胺瓷洹碧峁┝艘环N特殊的機制,可以偵測可用的方法,并產生方法名。通過Java Beans(第13章將詳細介紹),Java 1.1為這種基于組件的程序設計提供了一個基礎結構。在運行期查詢類信息的另一個原動力是通過網絡創(chuàng)建與執(zhí)行位于遠程系統(tǒng)上的對象。這就叫作“遠程方法調用”(RMI),它允許Java程序(版本1.1以上)使用由多臺機器發(fā)布或分布的對象。這種

24、對象的分布可能是由多方面的原因引起的:可能要做一件計算密集型的工作,想對它進行分割,讓處于空閑狀態(tài)的其他機器分擔部分工作,從而加快處理進度。某些情況下,可能需要將用于控制特定類型任務(比如多層客戶服務器架構中的“運作規(guī)則”)的代碼放置在一臺特殊的機器上,使這臺機器成為對那些行動進行描述的一個通用儲藏所。而且可以方便地修改這個場所,使其對系統(tǒng)內的所有方面產生影響(這是一種特別有用的設計思路,因為機器是獨立存在的,所以能輕易修改軟件?。7植际接嬎阋材芨浞值匕l(fā)揮某些專用硬件的作用,它們特別擅長執(zhí)行一些特定的任務例如矩陣逆轉但對常規(guī)編程來說卻顯得太夸張或者太昂貴了。在Java 1.1中,Class

25、類(本章前面已有詳細論述)得到了擴展,可以支持“反射”的概念。針對Field,Method以及Constructor類(每個都實現了Memberinterface成員接口),它們都新增了一個庫:。這些類型的對象都是JVM在運行期創(chuàng)建的,用于代表未知類里對應的成員。這樣便可用構建器創(chuàng)建新對象,用get()和set()方法讀取和修改與Field對象關聯(lián)的字段,以及用invoke()方法調用與Method對象關聯(lián)的方法。此外,我們可調用方法getFields(),getMethods(),getConstructors(),分別返回用于表示字段、方法以及構建器的對象數組(在聯(lián)機文檔中,還可找到與Cl

26、ass類有關的更多的資料)。因此,匿名對象的類信息可在運行期被完整的揭露出來,而在編譯期間不需要知道任何東西。大家要認識的很重要的一點是“反射”并沒有什么神奇的地方。通過“反射”同一個未知類型的對象打交道時,JVM只是簡單地檢查那個對象,并調查它從屬于哪個特定的類(就象以前的RTTI那樣)。但在這之后,在我們做其他任何事情之前,Class對象必須載入。因此,用于那種特定類型的.class文件必須能由JVM調用(要么在本地機器內,要么可以通過網絡取得)。所以RTTI和“反射”之間唯一的區(qū)別就是對RTTI來說,編譯器會在編譯期打開和檢查.class文件。換句話說,我們可以用“普通”方式調用一個對象

27、的所有方法;但對“反射”來說,.class文件在編譯期間是不可使用的,而是由運行期環(huán)境打開和檢查。一個類方法提取器很少需要直接使用反射工具;之所以在語言中提供它們,僅僅是為了支持其他Java特性,比如對象序列化(第10章介紹)、Java Beans以及RMI(本章后面介紹)。但是,我們許多時候仍然需要動態(tài)提取與一個類有關的資料。其中特別有用的工具便是一個類方法提取器。正如前面指出的那樣,若檢視類定義源碼或者聯(lián)機文檔,只能看到在那個類定義中被定義或覆蓋的方法,基礎類那里還有大量資料拿不到。幸運的是,“反射”做到了這一點,可用它寫一個簡單的工具,令其自動展示整個接口。下面便是具體的程序:533-5

28、34頁程序Class方法getMethods()和getConstructors()可以分別返回Method和Constructor的一個數組。每個類都提供了進一步的方法,可解析出它們所代表的方法的名字、參數以及返回值。但也可以象這樣一樣只使用toString(),生成一個含有完整方法簽名的字串。代碼剩余的部分只是用于提取命令行信息,判斷特定的簽名是否與我們的目標字串相符(使用indexOf()),并打印出結果。這里便用到了“反射”技術,因為由Class.forName()產生的結果不能在編譯期間獲知,所以所有方法簽名信息都會在運行期間提取。若研究一下聯(lián)機文檔中關于“反射”(Reflectio

29、n)的那部分文字,就會發(fā)現它已提供了足夠多的支持,可對一個編譯期完全未知的對象進行實際的設置以及發(fā)出方法調用。同樣地,這也屬于幾乎完全不用我們操心的一個步驟Java自己會利用這種支持,所以程序設計環(huán)境能夠控制Java Beans但它無論如何都是非常有趣的。一個有趣的試驗是運行java ShowMehods ShowMethods。這樣做可得到一個列表,其中包括一個public默認構建器,盡管我們在代碼中看見并沒有定義一個構建器。我們看到的是由編譯器自動合成的那一個構建器。如果隨之將ShowMethods設為一個非public類(即換成“友好”類),合成的默認構建器便不會在輸出結果中出現。合成的

30、默認構建器會自動獲得與類一樣的訪問權限。ShowMethods的輸出仍然有些“不爽”。例如,下面是通過調用得到的輸出結果的一部分:534-535頁程序若能去掉象java.lang這樣的限定詞,結果顯然會更令人滿意。有鑒于此,可引入上一章介紹的StreamTokenizer類,解決這個問題:535-537頁程序ShowMethodsClean方法非常接近前一個ShowMethods,只是它取得了Method和Constructor數組,并將它們轉換成單個String數組。隨后,每個這樣的String對象都在StripQualifiers.Strip()里“過”一遍,刪除所有方法限定詞。正如大家看

31、到的那樣,此時用到了StreamTokenizer和String來完成這個工作。假如記不得一個類是否有一個特定的方法,而且不想在聯(lián)機文檔里逐步檢查類結構,或者不知道那個類是否能對某個對象(如Color對象)做某件事情,該工具便可節(jié)省大量編程時間。第17章提供了這個程序的一個GUI版本,可在自己寫代碼的時候運行它,以便快速查找需要的東西。11.4 總結利用RTTI可根據一個匿名的基礎類句柄調查出類型信息。但正是由于這個原因,新手們極易誤用它,因為有些時候多形性方法便足夠了。對那些以前習慣程序化編程的人來說,極易將他們的程序組織成一系列switch語句。他們可能用RTTI做到這一點,從而在代碼開發(fā)和維護中損失多形性技術的重要價值。Java的要求是讓我

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論