您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Java異常處理機制的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
我們在寫代碼的時候都或多或少碰到了大大小小的異常,例如:
public class Test { public static void main(String[] args) { int[] arr = {1,2,3}; System.out.println(arr[5]); } }
當我們數(shù)組越界時,編譯器會給我們報數(shù)組越界,并提示哪行出了錯。
再比如:
class Test{ int num = 10; public static void main(String[] args) { Test test = null; System.out.println(test.num); } }
當我們嘗試用使用空對象時,編譯器也會報空指針異常:
那么究竟什么是異常?
所謂異常指的就是程序在 運行時 出現(xiàn)錯誤時通知調(diào)用者的一種機制 .
關鍵字 "運行時" ,有些錯誤是這樣的, 例如將 System.out.println 拼寫錯了, 寫成了
system.out.println. 此時編譯過程中就會出 錯, 這是 "編譯期" 出錯.
而運行時指的是程序已經(jīng)編譯通過得到 class 文件了 , 再由 JVM 執(zhí)行過程中出現(xiàn)的錯誤 .
Java異常處理依賴于5個關鍵字:try、catch、finally、throws、throw。下面來逐一介紹下。
①try:try塊中主要放置可能會產(chǎn)生異常的代碼塊。如果執(zhí)行try塊里的業(yè)務邏輯代碼時出現(xiàn)異
常,系統(tǒng)會自動生成一個異常對象,該異常對象被提交給運行環(huán)境,這個過程被稱為拋出
(throw)異常。Java環(huán)境收到異常對象時,會尋找合適的catch塊(在本方法或是調(diào)用方
法)。
②catch: catch 代碼塊中放的是出現(xiàn)異常后的處理行為,也可以寫此異常出錯的原因或者打
印棧上的錯誤信息。但catch語句不能為空,因為一旦將catch語句寫為空,就代表忽略了此
異常。如:
空的catch塊會使異常達不到應有的目的,即強迫你處理異常的情況。忽略異常就如同忽略
火警信號一樣——若把火警信號關掉了,當真正的火災發(fā)生時,就沒有人能看到火警信號
了?;蛟S你會僥幸逃過一劫,或許結(jié)果將是災難性的。每當見到空的catch塊時,我們都應該
警鐘長鳴。
當然也有一種情況可以忽略異常,即關閉fileinputstream(讀寫本地文件)的時候。因為你還
沒有改變文件的狀態(tài),因此不必執(zhí)行任何恢復動作,并且已經(jīng)從文件中讀取到所需要的信
息,因此不必終止正在進行的操作。
③finally:finally 代碼塊中的代碼用于處理善后工作, 會在最后執(zhí)行,也一定會被執(zhí)行。當遇
到try或catch中return或throw之類可以終止當前方法的代碼時,jvm會先去執(zhí)行finally中的語
句,當finally中的語句執(zhí)行完畢后才會返回來執(zhí)行try/catch中的return,throw語句。如果
finally中有return或throw,那么將執(zhí)行這些語句,不會在執(zhí)行try/catch中的return或throw語
句。finally塊中一般寫的是關閉資源之類的代碼。但是我們一般不在finally語句中加入return
語句,因為他會覆蓋掉try中執(zhí)行的return語句。例如:
finally將最后try執(zhí)行的return 10覆蓋了,最后結(jié)果返回了20.
④throws:在方法的簽名中,用于拋出此方法中的異常給調(diào)用者,調(diào)用者可以選擇捕獲或者
拋出,如果所有方法(包括main)都選擇拋出(或者沒有合適的處理異常的方式,即異常類
型不匹配)那么最終將會拋給JVM,就會像我們之前沒使用try、catch語句一樣。JVM打印出
棧軌跡(異常鏈)。
⑤throw:用于拋出一個具體的異常對象。常用于自定義異常類中。
ps:
關于 "調(diào)用棧",方法之間是存在相互調(diào)用關系的, 這種調(diào)用關系我們可以用 "調(diào)用棧" 來描述.
在 JVM 中有一塊內(nèi)存空間稱為 "虛擬機棧" 專門存儲方法之間的調(diào)用關系. 當代碼中出現(xiàn)異常
的時候, 我們就可以使用 e.printStackTrace() 的方式查看出現(xiàn)異常代碼的調(diào)用棧,一般寫在catch語句中。
程序先執(zhí)行 try 中的代碼
如果 try 中的代碼出現(xiàn)異常, 就會結(jié)束 try 中的代碼, 看和 catch 中的異常類型是否匹配.
如果找到匹配的異常類型, 就會執(zhí)行 catch 中的代碼
如果沒有找到匹配的異常類型, 就會將異常向上傳遞到上層調(diào)用者.
無論是否找到匹配的異常類型, finally 中的代碼都會被執(zhí)行到(在該方法結(jié)束之前執(zhí)行).
如果上層調(diào)用者也沒有處理的了異常, 就繼續(xù)向上傳遞.
一直到 main 方法也沒有合適的代碼處理異常, 就會交給 JVM 來進行處理, 此時程序就會異常終止.
存在即合理,舉個例子
//不使用異常 int[] arr = {1, 2, 3}; System.out.println("before"); System.out.println(arr[100]); System.out.println("after");
當我們不使用異常時,發(fā)現(xiàn)出現(xiàn)異常程序直接崩潰,后面的after也沒有打印。
//使用異常 int[] arr = {1, 2, 3}; try { System.out.println("before"); System.out.println(arr[100]); System.out.println("after"); } catch (ArrayIndexOutOfBoundsException e) { // 打印出現(xiàn)異常的調(diào)用棧 e.printStackTrace(); } System.out.println("after try catch");
當我們使用了異常,雖然after也沒有執(zhí)行,但程序并沒有直接崩潰,后面的sout語句還是執(zhí)行了
這不就是異常的作用所在嗎?
再舉個例子,當玩王者榮耀時,突然斷網(wǎng),他不會讓你直接程序崩潰吧,而是給你斷線重連的機會吧:
我們再用偽代碼演示一把王者榮耀的對局過程:
不使用異常處理 boolean ret = false; ret = 登陸游戲(); if (!ret) { 處理登陸游戲錯誤; return; } ret = 開始匹配(); if (!ret) { 處理匹配錯誤; return; } ret = 游戲確認(); if (!ret) { 處理游戲確認錯誤; return; } ret = 選擇英雄(); if (!ret) { 處理選擇英雄錯誤; return; } ret = 載入游戲畫面(); if (!ret) { 處理載入游戲錯誤; return; } ......
使用異常處理 try { 登陸游戲(); 開始匹配(); 游戲確認(); 選擇英雄(); 載入游戲畫面(); ... } catch (登陸游戲異常) { 處理登陸游戲異常; } catch (開始匹配異常) { 處理開始匹配異常; } catch (游戲確認異常) { 處理游戲確認異常; } catch (選擇英雄異常) { 處理選擇英雄異常; } catch (載入游戲畫面異常) { 處理載入游戲畫面異常; } ......
我們能明顯的看到不使用異常時,正確流程和錯誤處理代碼混在一起,不易于分辨,而用了
異常后,能更易于理解代碼。
當然使用異常的好處還遠不止于此,我們可以在try、catch語句中加入信息提醒功能,比如你
開發(fā)了一個軟件,當那個軟件出現(xiàn)異常時,發(fā)個信息提醒你及時去修復。博主就做了一個小
小的qq郵箱信息提醒功能,源碼在碼云,有興趣的可以去看看呀!需要配置qq郵箱pop3服
務,友友們可以去查查怎么開啟呀,我們主旨不是這個所以不教怎么開啟了。演示一下:
別群發(fā)消息哦,不然可能會被封號???
try{ int i = 0; while(true) System.out.println(a[i++]); }catch(ArrayIndexOutOfBoundsException e){ }
這段代碼有什么用?看起來根本不明顯,這正是它沒有真正被使用的原因。事實證明,作為
一個要對數(shù)組元素進行遍歷的實現(xiàn)方式,它的構(gòu)想是非常拙劣的。當這個循環(huán)企圖訪問數(shù)組
邊界之外的第一個數(shù)組元素時,用拋出(throw)、捕獲(catch)、
忽略(ArrayIndexOutOfBoundsException)的手段來達到終止無限循環(huán)的目的。假定它與數(shù)
組循環(huán)是等價的,對于任何一個Java程序員來講,下面的標準模式一看就會明白:
for(int m : a) System.out.println(m);
為什么優(yōu)先異常的模式,而不是用行之有效標準模式呢?
可能是被誤導了,企圖利用異常機制提高性能,因為jvm每次訪問數(shù)組都需要判斷下標是否越
界,他們認為循環(huán)終止被隱藏了,但是在foreach循環(huán)中仍然可見,這無疑是多余的,應該避
免。
上面想法有三個錯誤:
1.異常機制設計的初衷是用來處理不正常的情況,所以JVM很少對它們進行優(yōu)化。
2.代碼放在try…catch中反而阻止jvm本身要執(zhí)行的某些特定優(yōu)化。
3.對數(shù)組進行遍歷的標準模式并不會導致冗余的檢查。
這個例子的教訓很簡單:顧名思義,異常應只用于異常的情況下,它們永遠不應該用于正常
的控制流。
總結(jié):異常是為了在異常情況下使用而設計的,不要用于一般的控制語句。
在Java中提供了三種可拋出結(jié)構(gòu):受查異常(checked exception)、運行時異常(run-time exception)和錯誤(error)。
(補充)
什么是受查異常?只要不是派生于error或runtime的異常類都是受查異常。舉個例子:
我們自定義兩個異常類和一個接口,以及一個測試類
interface IUser { void changePwd() throws SafeException,RejectException; } class SafeException extends Exception {//因為繼承的是execption,所以是受查異常類 public SafeException() { } public SafeException(String message) { super(message); } } class RejectException extends Exception {//因為繼承的是execption,所以是受查異常類 public RejectException() { } public RejectException(String message) { super(message); } } public class Test { public static void main(String[] args) { IUser user = null; user.changePwd(); } }
我們發(fā)現(xiàn)test測試類中user使用方法報錯了,因為java認為checked異常都是可以再編譯階
段被處理的異常,所以它強制程序處理所有的checked異常,java程序必須顯式處checked
異常,如果程序沒有處理,則在編譯時會發(fā)生錯誤,無法通過編譯。
①try、catch包裹
IUser user = null; try { user.changePwd(); }catch (SafeException e){ e.printStackTrace(); } catch (RejectException e){ e.printStackTrace(); }
②拋出異常,將處理動作交給上級調(diào)用者,調(diào)用者在調(diào)用這個方法時還是要寫一遍try、catch
包裹語句的,所以這個其實是相當于聲明,讓調(diào)用者知道這個函數(shù)需要拋出異常
public static void main(String[] args) throws SafeException, RejectException { IUser user = null; user.changePwd(); }
派生于error或runtime類的所有異常類就是非受查異常。
可以這么說,我們現(xiàn)在寫程序遇到的異常大部分都是非受查異常,程序直接崩潰,后面的也
不執(zhí)行。
像空指針異常、數(shù)組越界異常、算術異常等,都是非受查異常。由編譯器運行時給你檢查出
來的,所以也叫作運行時異常。
如果不能阻止異常條件的產(chǎn)生,并且一旦產(chǎn)生異常,程序員可以立即采取有用的動作,這種
受查異常才是可取的。否則,更適合用非受查異常。這種例子就是
CloneNotSuppportedException(受查異常)。它是被Object.clone拋出來的,Object.clone
只有在實現(xiàn)了Cloneable的對象上才可以被調(diào)用。
被一個方法單獨拋出的受查異常,會給程序員帶來非常高的額外負擔,如果這個方法還有其
他的受查異常,那么它被調(diào)用是一定已經(jīng)出現(xiàn)在一個try塊中,所以這個異常只需要另外一個
catch塊。但當只拋出一個受查異常時,僅僅一個異常就會導致該方法不得不處于try塊中,也
就導致了使用這個方法的類都不得不使用try、catch語句,使代碼可讀性也變低了。
受查異常使接口聲明脆弱,比如一開始一個接口只有一個聲明異常
interfaceUser{ //修改用戶名,拋出安全異常 publicvoid changePassword() throws MySecurityExcepiton; }
但隨著系統(tǒng)開發(fā),實現(xiàn)接口的類越來越多,突然發(fā)現(xiàn)changePassword還需要拋出另一個異
常,那么實現(xiàn)這個接口的所有類也都要追加對這個新異常的處理,這個工程量就很大了。
總結(jié):如果不是非用不可,盡量使用非受查異常,或?qū)⑹懿楫惓^D(zhuǎn)為非受查異常。
我們用自定義異常來實現(xiàn)一個登錄報錯的小應用
class NameException extends RuntimeException{//用戶名錯誤異常 public NameException(String message){ super(message); } } class PasswordException extends RuntimeException{//密碼錯誤異常 public PasswordException(String message){ super(message); } }
test類來測試運行
public class Test { private static final String name = "bit"; private static final String password ="123"; public static void Login(String name,String password) throws NameException,PasswordException{ try{ if(!Test.name.equals(name)){ throw new NameException("用戶名錯誤!"); } }catch (NameException e){ e.printStackTrace(); } try { if(!Test.password.equals(password)){ throw new PasswordException("密碼錯誤"); } }catch (PasswordException e){ e.printStackTrace(); } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); String password = scanner.nextLine(); Login(name,password); } }
關于異常就到此為止了,怎么感覺還有點意猶未盡呢?
感謝各位的閱讀!關于“Java異常處理機制的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。