有些代碼必須捕獲異常,捕獲異常需要進(jìn)行周密的計(jì)劃。
如果某個(gè)異常發(fā)生的時(shí)候沒有在任何地方進(jìn)行捕獲,那程序就會(huì)終止執(zhí)行,并在控制臺(tái)上打印出異常信息,其中包括異常的類型和堆棧的內(nèi)容。對(duì)于圖形界面程序(applet和application應(yīng)用程序),在捕獲異常之后,也會(huì)打印出堆棧的信息,但程序?qū)⒎祷氐接脩艚缑娴奶幚硌h(huán)中(在調(diào)試基于圖形界面的程序時(shí),最好保證控制臺(tái)窗口可見,并且沒有被極小化)。
捕獲一個(gè)異常,必須設(shè)置try/catch語(yǔ)句塊,如果在try語(yǔ)句塊中的任何代碼拋出了一個(gè)在catch+句中說(shuō)明的異常類,那么:
1.程序?qū)⑻^(guò)try語(yǔ)句塊的其余代碼。
2.程序?qū)?zhí)行catch子句中的處理器代碼。
如果在try語(yǔ)句塊中的代碼沒有拋出任何異常,那么程序?qū)⑻^(guò)catch子句。
如果方法中的任何代碼拋出了一個(gè)在catch+句中沒有聲明的異常類型,那么這個(gè)方法就會(huì)立刻退出(期待調(diào)用者為這種類型的異常設(shè)計(jì)了catch子句)。
需要注意,try語(yǔ)句中的大多數(shù)代碼都很容易理解:讀取并處理文本行,直到遇到文件結(jié)束符為止。正如在JavaAPI中看到的那樣,read方法有可能拋出一個(gè)IOException異常。在這種情況下,將跳出整個(gè)while循環(huán),進(jìn)入catch子句,并輸出堆棧情況。對(duì)于一個(gè)普通的程序來(lái)說(shuō),這樣處理異?;旧虾虾跚槔?。還有其他的選擇嗎?
通常,最好的選擇是什么也不做,而是將異常傳遞給調(diào)用者。如果read方法出現(xiàn)了錯(cuò)誤,就讓read方法的調(diào)用者去操心!如果采用這種處理方式,就必須聲明這個(gè)方法可能會(huì)拋出一個(gè)IOException。
請(qǐng)記住,編譯器嚴(yán)格地執(zhí)行throws說(shuō)明符。如果調(diào)用了一個(gè)拋出已檢查異常的方法,就必須對(duì)它進(jìn)行處理,或者將它傳遞出去。
哪種方法更好呢?通常,應(yīng)該捕獲那些知道如何處理的異常,而將那些不知道怎樣處理的異常傳遞出去。如果想將異常傳遞出去,就必須在方法的首部添加一個(gè)throws說(shuō)明符以便告知調(diào)用者這個(gè)方法可能會(huì)拋出異常。
仔細(xì)閱讀一下JavaAPI文檔,以便知道每個(gè)方法可能會(huì)拋出哪種異常,然后再?zèng)Q定是自己處理,還是添加到throws列表。對(duì)于后一種情況,也不必猶豫。將異常直接交給能夠勝任的處理器進(jìn)行處理要比壓制對(duì)它的處理更好。
同時(shí)請(qǐng)記住,這個(gè)規(guī)則也有一個(gè)例外。如果編寫一個(gè)覆蓋超類的方法,而這個(gè)方法又沒有拋出異常,那么這個(gè)方法就必須捕獲方法代碼中出現(xiàn)的每一個(gè)已檢查異常。不允許在子類的thrws說(shuō)明符中出現(xiàn)超過(guò)超類方法所列出的異常類范圍。
假設(shè)在一個(gè)Java程序運(yùn)行期間出現(xiàn)了一個(gè)錯(cuò)誤。這個(gè)錯(cuò)誤可能是由于文件包含了錯(cuò)誤的信息,或者網(wǎng)絡(luò)連接出現(xiàn)問(wèn)題造成的,也有可能是因?yàn)槭褂脽o(wú)效的數(shù)組下標(biāo),或者試圖使用一個(gè)沒有被賦值引用而造成的。用戶期望在出現(xiàn)錯(cuò)誤時(shí),程序能夠采用一些理智的行為,如果由于出現(xiàn)錯(cuò)誤而使得某些操作沒有完成,程序應(yīng)該:返回到一種安全狀態(tài),并能夠讓用戶執(zhí)行一些其他的命令;或者允許用戶保存所有操作的結(jié)果,并以適當(dāng)?shù)姆绞浇K止程序。
要做到這些并不是一件很容易的事情。其原因是檢測(cè)(或引發(fā))錯(cuò)誤條件的代碼通常離那些能夠讓數(shù)據(jù)恢復(fù)到安全狀態(tài),或者能夠保存用戶的操作結(jié)果,并正常地退出程序的代碼很遠(yuǎn)。異常處理的任務(wù)就是將控制權(quán)從錯(cuò)誤產(chǎn)生的地方轉(zhuǎn)移給能夠處理這種情況的錯(cuò)誤處理器。為了能夠在程序中處理異常情況,必須研究程序中可能會(huì)出現(xiàn)的錯(cuò)誤和問(wèn)題,以及哪類問(wèn)題需要關(guān)注。
1.用戶輸入錯(cuò)誤
除了那些不可避免的打字錄入外,有些用戶喜歡各行其是,不遵守程序的要求。例如,假設(shè)有一個(gè)用戶請(qǐng)求連接一個(gè)URL,而語(yǔ)法卻不正確。在程序代碼中應(yīng)該對(duì)此進(jìn)行檢查,如果沒有檢查,網(wǎng)絡(luò)數(shù)據(jù)包就會(huì)給出警告。
2.設(shè)備錯(cuò)誤
硬件并不總是讓它做什么,它就做什么。打印機(jī)可能被關(guān)掉了。網(wǎng)頁(yè)可能臨時(shí)性地不能瀏覽。在一個(gè)任務(wù)的處理過(guò)程中,硬件經(jīng)常出現(xiàn)問(wèn)題。例如,打印機(jī)在打印過(guò)程中可能沒有紙了。
3.物理限制
磁盤滿了,可用存儲(chǔ)空間已被用完。
4.代碼錯(cuò)誤
程序方法有可能無(wú)法正確的執(zhí)行。例如,方法可能返回了一個(gè)錯(cuò)誤的答案,或者錯(cuò)誤地調(diào)用了其他方法。使用了一個(gè)無(wú)效的數(shù)組下標(biāo),試圖查找一個(gè)在散列表中不存在的數(shù)據(jù)項(xiàng)以及試圖對(duì)一個(gè)空棧進(jìn)行退棧操作。
對(duì)于方法中出現(xiàn)的錯(cuò)誤,傳統(tǒng)的處理方式是返回一個(gè)特定的錯(cuò)誤編碼,調(diào)用這個(gè)方法的方法對(duì)其進(jìn)行分析。例如,對(duì)于一個(gè)從文件中讀取信息的方法來(lái)說(shuō),如果返回值不是標(biāo)準(zhǔn)字符,而是一個(gè)-1,則表示文件結(jié)束。這種處理方式對(duì)于很多異常狀況都是可行的。還有一種表示錯(cuò)誤狀況的常用返回值是null引用。當(dāng)希望查詢的參數(shù)不存在時(shí),這個(gè)方法就會(huì)返回null。
遺憾的是,并不是在任何情況下都能夠返回一個(gè)錯(cuò)誤編碼。有可能無(wú)法明確地將有效數(shù)據(jù)與無(wú)效數(shù)據(jù)加以區(qū)分。一個(gè)返回整型的方法就不能簡(jiǎn)單地通過(guò)返回-1表示錯(cuò)誤,因?yàn)?1很可能是一個(gè)完全合法的結(jié)果。
在Java中,如果某個(gè)方法不能夠采用正常的途徑完整它的任務(wù),就可以通過(guò)另外一個(gè)路徑退出方法。在這種情況下,方法并不返回任何值,而是拋出(throw)一個(gè)封裝了錯(cuò)誤信息的對(duì)象。需要注意的是,這個(gè)方法將會(huì)立刻退出,并不返回任何值。此外,調(diào)用這個(gè)方法的代碼也將無(wú)法繼續(xù)執(zhí)行,而是,異常處理機(jī)制開始搜索能夠處理這種異常狀況的異常處理器(exceptionhandler),異常具有自己的語(yǔ)法和特定的繼承結(jié)構(gòu)。
如果只希望用戶能夠點(diǎn)擊按鈕或菜單,那幺就不需要顯式地處理鼠標(biāo)事件。鼠標(biāo)操作將由用戶界面中的各種組件內(nèi)部處理。然而,如果希望用戶使用鼠標(biāo)畫圖,就需要補(bǔ)貨鼠標(biāo)移動(dòng)點(diǎn)擊和拖動(dòng)事件。
當(dāng)用戶點(diǎn)擊鼠標(biāo)按鈕時(shí),將會(huì)調(diào)用三個(gè)監(jiān)聽器方法:鼠標(biāo)第一次被按下時(shí)調(diào)用mousepressed;鼠標(biāo)被釋放時(shí)調(diào)用mousereleased;最后調(diào)用mouseclicked。如果只對(duì)最終的點(diǎn)擊事件感興趣,就可以忽略前兩個(gè)方法。用mouseevent類對(duì)象作為參數(shù),調(diào)用getX和gety方法可以獲得鼠標(biāo)被按下時(shí)鼠標(biāo)指針?biāo)诘膞和y坐標(biāo)。要想?yún)^(qū)分單擊,雙擊和三擊,需要使用getclickcount方法。
有些用戶界面設(shè)計(jì)者喜歡讓用戶采用鼠標(biāo)點(diǎn)擊與鍵盤修飾符組合(例如,control+shift+click)的方式進(jìn)行操作。我們感覺這并不是一種值得贊許的方式。如果對(duì)此持有不同的觀點(diǎn),可以看一看同時(shí)檢測(cè)鼠標(biāo)按鍵和鍵盤修飾符所帶來(lái)的混亂。
需要注意,在Windows環(huán)境下,使用BUTTON3_DOWN_MASK檢測(cè)鼠標(biāo)右鍵(非主要的)狀態(tài)。
當(dāng)鼠標(biāo)在窗口上移動(dòng)時(shí),窗口將會(huì)收到一連串的鼠標(biāo)移動(dòng)事件。請(qǐng)注意:有兩個(gè)獨(dú)立的接口mouseListener和mouseMotionListener。這樣做有利于提高效率。當(dāng)用戶移動(dòng)鼠標(biāo)時(shí),只關(guān)心鼠標(biāo)點(diǎn)擊(clicks)的監(jiān)聽器就不會(huì)被多余的鼠標(biāo)移動(dòng)(moves)所困擾。
通常,激活一個(gè)命令可以有多種方式,用戶可以通過(guò)菜單、擊鍵或工具欄上的按鈕選擇特定的功能。在AWT事件模型中實(shí)現(xiàn)這些非常容易:將所有事件 連接到同一個(gè)監(jiān)聽器上。例如:假設(shè)blueAction是一個(gè)動(dòng)作監(jiān)聽器,它的actionPerformed方法可以將背景顏色改變成藍(lán)色。將一個(gè)監(jiān)聽 器對(duì)象加到下面幾個(gè)事件源上:
·標(biāo)記為Blue的工具欄按鈕
·標(biāo)記為Blue的菜單項(xiàng)
·擊鍵CTRL+B
然后,無(wú)論改變背景顏色的命令是通過(guò)哪種方式下達(dá)的,是點(diǎn)擊按鈕、菜單選擇,還是按下鍵盤,其操作動(dòng)作都是一樣的。
Swing包提供了一種非常實(shí)用的機(jī)制來(lái)封裝命令,并將它們連接到多個(gè)事件源,這就是Action接口。一個(gè)動(dòng)作是一個(gè)封裝下列內(nèi)容的對(duì)象:
·命令的說(shuō)明(一個(gè)文本字符串和一個(gè)可選圖標(biāo));
執(zhí)行命令所需要的參數(shù)(例如,在列舉的例子中請(qǐng)求改變的顏色)。
第一個(gè)方法是ActionListener接口中很熟悉的一個(gè):實(shí)際上,Action接口擴(kuò)展于ActionListener接口,因此,可以在任何需要ActionListener對(duì)象的地方使用Action對(duì)象。
接下來(lái)的兩個(gè)方法允許啟用或禁用這個(gè)動(dòng)作,并檢查這個(gè)動(dòng)作當(dāng)前是否啟用。當(dāng)一個(gè)連接到菜單或工具欄上的動(dòng)作被禁用時(shí),這個(gè)選項(xiàng)就會(huì)變成灰色。
putValue和getvalue方法允許存儲(chǔ)和檢索動(dòng)作對(duì)象中的任意名/值。有兩個(gè)重要的預(yù)定義字符串:Action.NAME和Action.sMALLjcON,用于將動(dòng)作的名字和圖標(biāo)存儲(chǔ)到一個(gè)動(dòng)作對(duì)象中。
如果動(dòng)作對(duì)象添加到菜單或工具欄上,它的名稱和圖標(biāo)就會(huì)被自動(dòng)地提取出來(lái),并顯示在菜單項(xiàng)或工具欄中。SHORT_DESCRiRTION值變成了工具提示。
Action接口的最后兩個(gè)方法能夠讓其他對(duì)象在動(dòng)作對(duì)象的屬性發(fā)生變化時(shí)得到通告,尤其是菜單或工具欄處罰的動(dòng)作。例如,如果增加一個(gè)菜單,作為動(dòng)作 對(duì)象的屬性變更監(jiān)聽器,而這個(gè)動(dòng)作對(duì)象隨后被禁用,菜單就會(huì)被調(diào)用,并將動(dòng)作名稱變?yōu)榛疑?。屬性變更監(jiān)聽器是一種常用的構(gòu)造形式,它是JavaBeans 組件模型的一部分。
需要注意,Action一個(gè)接口,而不是一個(gè)類。實(shí)現(xiàn)這個(gè)接口的所有類都必須實(shí) 現(xiàn)剛才討論的7個(gè)方法。慶幸的是,有一個(gè)類實(shí)現(xiàn)了這個(gè)接口除actionPerformed方法之外的所有方法,它就是AbstractAction個(gè)類 存儲(chǔ)了所有名/值對(duì),并管理著屬性變更監(jiān)聽器。我們可以直接擴(kuò)展AbstractAction類,并在擴(kuò)展類中實(shí)現(xiàn)actionPerformed方法。
構(gòu)造器讀取動(dòng)作的名稱和圖標(biāo),為工具提示設(shè)置簡(jiǎn)要說(shuō)明,將工作設(shè)置為監(jiān)聽器。
最后,想要將這個(gè)動(dòng)作對(duì)象添加到擊鍵中,以便讓用戶敲擊鍵盤命令來(lái)執(zhí)行這項(xiàng)動(dòng)作。為了將動(dòng)作與擊鍵關(guān)聯(lián)起來(lái),首先需要生成KeyStroke類對(duì)象。這 是一個(gè)很有用的類,它封裝了對(duì)鍵的說(shuō)明。要想生成一個(gè)Keystroke對(duì)象不要調(diào)用構(gòu)造器,而是調(diào)用KeyStroke類中的靜態(tài) getKeyStroke方法:
為了能夠理解下一個(gè)步驟,需要知道keyboardfocus的概 念。用戶界面中可以包含許多按鈕、菜單、滾動(dòng)欄以及其他的組件。當(dāng)用戶敲擊鍵盤時(shí),這個(gè)動(dòng)作會(huì)被發(fā)送給擁有焦點(diǎn)的組件。通常具有焦點(diǎn)的組件可以明顯地察覺 到(但并不總是這樣),例如,在Java觀感中,具有焦點(diǎn)的按鈕在按鈕文本周圍有一個(gè)細(xì)的矩形邊框。用戶可以使用TAB鍵在組件之間移動(dòng)焦點(diǎn)。當(dāng)按下 SPACE鍵時(shí),就點(diǎn)擊了擁有焦點(diǎn)的按鈕。還有一些鍵執(zhí)行一些其他的動(dòng)作,例如,按下箭頭鍵可以移動(dòng)滾動(dòng)條。
然而,在這里的示例中,并不希望將擊鍵發(fā)送給擁有焦點(diǎn)的組件。否則,每個(gè)按鈕都需要知道如何處理CTRL+Y、CTRL+B和CTRL+R這些組合鍵。
這是一個(gè)常見的問(wèn)題,Swing設(shè)計(jì)者給出了一種很便捷的解決方案。每個(gè)JComponent有三個(gè)輸入映射(imput maps),每一個(gè)映射的KeyStroke對(duì)象都與動(dòng)作關(guān)聯(lián)。
對(duì)于圖形用戶界面的程序來(lái)說(shuō),事件處理是十分重要的。要想實(shí)現(xiàn)用戶界面,必須掌握J(rèn)ava事件處理的基本方法。
任何支持GUI的操作環(huán)境都要不斷地監(jiān)視敲擊鍵盤或點(diǎn)擊鼠標(biāo)這樣的事件。操作環(huán)境將這些事件報(bào)告給正在運(yùn)行的應(yīng)用程序。如果有事件產(chǎn)生,每個(gè)應(yīng)用程序?qū)? 決定如何對(duì)它們作出響應(yīng)。在VisualBasic這樣的語(yǔ)言中,事件與代碼之間有著明確的對(duì)應(yīng)關(guān)系。程序員對(duì)相關(guān)的特定事件編寫代碼,并將這些代碼放置 在過(guò)程中,通常人們將它們稱為事件過(guò)程(event procedure)。例如,有一個(gè)名為HelpButton的VisualBasic按鈕有一個(gè)與之關(guān)聯(lián)的HelpButton_Click時(shí)間過(guò)程。 這個(gè)過(guò)程中的代碼將在點(diǎn)擊按鈕后執(zhí)行。每個(gè)VisualBasic的GUI組件都響應(yīng)一個(gè)固定的事件集,不可能改變VisualBasic組件響應(yīng)的事件 集。
另一方面,如果使用像原始的C這樣的語(yǔ)言進(jìn)行事件驅(qū)動(dòng)的程序設(shè)計(jì),那就需要編寫代碼來(lái) 不斷地檢查事件隊(duì)列,以便查詢操作環(huán)境報(bào)告的內(nèi)容(通常這些代碼被放置在包含很多switch語(yǔ)句的循環(huán)體申)。顯然,這種方式編寫的程序可讀性很差,而 且在有些情況下,編碼的難度也非常大。它的好處在于響應(yīng)的事件不受限制,而不像VisualBasic這樣的語(yǔ)言,將事件隊(duì)列對(duì)程序員隱藏起來(lái)。
Java程序設(shè)計(jì)環(huán)境折中了VisualBasic與原始C的事件處理方式,因此,它既有著強(qiáng)大的功能,又具有一定的復(fù)雜性。在AWT所知的事件范圍 內(nèi),完全可以控制事件從事件源(event source)例如,按鈕或滾動(dòng)條,到事件監(jiān)聽器(event listener)的傳遞過(guò)程,并將任何對(duì)象指派給事件監(jiān)聽器。不過(guò)事實(shí)上,應(yīng)該選擇一個(gè)能夠便于響應(yīng)事件的對(duì)象。這種事件委托模型(event delegation model)與VisualBasic那種預(yù)定義監(jiān)聽器模型比較起來(lái)更加靈活。
事件源有一些向其注冊(cè)事件監(jiān)聽器的方法。當(dāng)某個(gè)事件源產(chǎn)生事件時(shí),事件源會(huì)向?yàn)槭录?cè)的所有事件監(jiān)聽器對(duì)象發(fā)送一個(gè)通告。
像Java這樣的面向?qū)ο笳Z(yǔ)言,都將事件的相關(guān)信息封裝在一個(gè)事件對(duì)象(event object)中,在Java中,所有的事件對(duì)象都最終派生于java.util.EventObject類。當(dāng)然,每個(gè)事件類型還有子類,例 如,ActionEvent和WindowEvent。
不同的事件源可以產(chǎn)生不同類別的事件。例如,按鈕可以發(fā)送一個(gè)ActionEvent對(duì)象,而窗扣可以發(fā)送WindowEvent對(duì)象。
綜上所述,下面給出AWT事件處理機(jī)制的概要:
監(jiān)聽器對(duì)象是一個(gè)實(shí)現(xiàn)了特定監(jiān)聽器接口(listener interface)的類的實(shí)例。
事件源是一個(gè)能夠注冊(cè)監(jiān)聽器對(duì)象并發(fā)送事件對(duì)象的對(duì)象。
當(dāng)事件發(fā)生時(shí),事件源將事件對(duì)象傳遞給所有注冊(cè)的監(jiān)聽器。
監(jiān)聽器對(duì)象將利用事件對(duì)象中的信息決定如何對(duì)事件做出響應(yīng)。