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