目錄
1.初識例外
2.例外的基本用法
例外處理流程
3.為什么要使用例外?
例外應只用于例外的情況
4. 例外的種類
4.1 受查例外
解決方案:
4.2非受查例外
5.如何使用例外
避免不必要的使用受查例外
6.自定義例外
1.初識例外
我們在寫代碼的時候都或多或少碰到了大大小小的例外,例如:
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 執行程序中出現的錯誤 .
2.例外的基本用法
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 來進行處理, 此時程式就會例外終止.
3.為什么要使用例外?
存在即合理,舉個例子
//不使用例外 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.對陣列進行遍歷的標準模式并不會導致冗余的檢查,
這個例子的教訓很簡單:顧名思義,例外應只用于例外的情況下,它們永遠不應該用于正常
的控制流,
總結:例外是為了在例外情況下使用而設計的,不要用于一般的控制陳述句,
4. 例外的種類
在Java中提供了三種可拋出結構:受查例外(checked exception)、運行時例外(run-time exception)和錯誤(error),
(補充)
4.1 受查例外
什么是受查例外?只要不是派生于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(); }
4.2非受查例外
派生于error或runtime類的所有例外類就是非受查例外,
可以這么說,我們現在寫程式遇到的例外大部分都是非受查例外,程式直接崩潰,后面的也
不執行,
像空指標例外、陣列越界例外、算術例外等,都是非受查例外,由編譯器運行時給你檢查出
來的,所以也叫作運行時例外,
5.如何使用例外
避免不必要的使用受查例外
如果不能阻止例外條件的產生,并且一旦產生例外,程式員可以立即采取有用的動作,這種
受查例外才是可取的,否則,更適合用非受查例外,這種例子就是
CloneNotSuppportedException(受查例外),它是被Object.clone拋出來的,Object.clone
只有在實作了Cloneable的物件上才可以被呼叫,
被一個方法單獨拋出的受查例外,會給程式員帶來非常高的額外負擔,如果這個方法還有其
他的受查例外,那么它被呼叫是一定已經出現在一個try塊中,所以這個例外只需要另外一個
catch塊,但當只拋出一個受查例外時,僅僅一個例外就會導致該方法不得不處于try塊中,也
就導致了使用這個方法的類都不得不使用try、catch陳述句,使代碼可讀性也變低了,
受查例外使介面宣告脆弱,比如一開始一個介面只有一個宣告例外
interfaceUser{ //修改用戶名,拋出安全例外 publicvoid changePassword() throws MySecurityExcepiton; }但隨著系統開發,實作介面的類越來越多,突然發現changePassword還需要拋出另一個異
常,那么實作這個介面的所有類也都要追加對這個新例外的處理,這個工程量就很大了,
總結:如果不是非用不可,盡量使用非受查例外,或將受查例外轉為非受查例外,
6.自定義例外
我們用自定義例外來實作一個登錄報錯的小應用
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); } }
關于例外就到此為止了,怎么感徑訓有點意猶未盡呢?
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/402728.html
標籤:java










(補充)







