您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Java中的異常和異常處理知識點,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
一、異常簡介
什么是異常?
異常就是有異于常態,和正常情況不一樣,有錯誤出錯。在java中,阻止當前方法或作用域的情況,稱之為異常。
java中異常的體系是怎么樣的呢?
1.Java中的所有不正常類都繼承于Throwable類。Throwable主要包括兩個大類,一個是Error類,另一個是Exception類;
2.其中Error類中包括虛擬機錯誤和線程死鎖,一旦Error出現了,程序就徹底的掛了,被稱為程序終結者;
3.Exception類,也就是通常所說的“異常”。主要指編碼、環境、用戶操作輸入出現問題,Exception主要包括兩大類,非檢查異常(RuntimeException)和檢查異常(其他的一些異常)
4.RuntimeException異常主要包括以下四種異常(其實還有很多其他異常,這里不一一列出):空指針異常、數組下標越界異常、類型轉換異常、算術異常。RuntimeException異常會由java虛擬機自動拋出并自動捕獲(就算我們沒寫異常捕獲語句運行時也會拋出錯誤!!),此類異常的出現絕大數情況是代碼本身有問題應該從邏輯上去解決并改進代碼。
5.檢查異常,引起該異常的原因多種多樣,比如說文件不存在、或者是連接錯誤等等。跟它的“兄弟”RuntimeException運行異常不同,該異常我們必須手動在代碼里添加捕獲語句來處理該異常,這也是我們學習java異常語句中主要處理的異常對象。
二、try-catch-finally語句
(1)try塊:負責捕獲異常,一旦try中發現異常,程序的控制權將被移交給catch塊中的異常處理程序。
【try語句塊不可以獨立存在,必須與 catch 或者 finally 塊同存】
(2)catch塊:如何處理?比如發出警告:提示、檢查配置、網絡連接,記錄錯誤等。執行完catch塊之后程序跳出catch塊,繼續執行后面的代碼。
【編寫catch塊的注意事項:多個catch塊處理的異常類,要按照先catch子類后catch父類的處理方式,因為會【就近處理】異常(由上自下)。】
(3)finally:最終執行的代碼,用于關閉和釋放資源。
語法格式如下:
try{ //一些會拋出的異常 }catch(Exception e){ //第一個catch //處理該異常的代碼塊 }catch(Exception e){ //第二個catch,可以有多個catch //處理該異常的代碼塊 }finally{ //最終要執行的代碼 }
當異常出現時,程序將終止執行,交由異常處理程序(拋出提醒或記錄日志等),異常代碼塊外代碼正常執行。 try會拋出很多種類型的異常,由多個catch塊捕獲多鐘錯誤。
多重異常處理代碼塊順序問題:先子類再父類(順序不對編譯器會提醒錯誤),finally語句塊處理最終將要執行的代碼。
接下來,我們用實例來鞏固try-catch語句吧~
先看例子:
package com.hysum.test; public class TryCatchTest { /** * divider:除數 * result:結果 * try-catch捕獲while循環 * 每次循環,divider減一,result=result+100/divider * 如果:捕獲異常,打印輸出“異常拋出了”,返回-1 * 否則:返回result * @return */ public int test1(){ int divider=10; int result=100; try{ while(divider>-1){ divider--; result=result+100/divider; } return result; }catch(Exception e){ e.printStackTrace(); System.out.println("異常拋出了!!"); return -1; } } public static void main(String[] args) { // TODO Auto-generated method stub TryCatchTest t1=new TryCatchTest(); System.out.println("test1方法執行完畢!result的值為:"+t1.test1()); } }
運行結果:
結果分析:結果中的紅色字拋出的異常信息是由e.printStackTrace()來輸出的,它說明了這里我們拋出的異常類型是算數異常,后面還跟著原因:by zero(由0造成的算數異常),下面兩行at表明了造成此異常的代碼具體位置。
在上面例子中再加上一個test2()方法來測試finally語句的執行狀況:
/** * divider:除數 * result:結果 * try-catch捕獲while循環 * 每次循環,divider減一,result=result+100/divider * 如果:捕獲異常,打印輸出“異常拋出了”,返回result=999 * 否則:返回result * finally:打印輸出“這是finally,哈哈哈!!”同時打印輸出result * @return */ public int test2(){ int divider=10; int result=100; try{ while(divider>-1){ divider--; result=result+100/divider; } return result; }catch(Exception e){ e.printStackTrace(); System.out.println("異常拋出了!!"); return result=999; }finally{ System.out.println("這是finally,哈哈哈!!"); System.out.println("result的值為:"+result); } } public static void main(String[] args) { // TODO Auto-generated method stub TryCatchTest t1=new TryCatchTest(); //System.out.println("test1方法執行完畢!result的值為:"+t1.test1()); t1.test2(); System.out.println("test2方法執行完畢!"); }
運行結果:
結果分析:我們可以從結果看出,finally語句塊是在try塊和catch塊語句執行之后最后執行的。finally是在return后面的表達式運算后執行的(此時并沒有返回運算后的值,而是先把要返回的值保存起來,管finally中的代碼怎么樣,返回的值都不會改變,仍然是之前保存的值),所以函數返回值是在finally執行前確定的;
這里有個有趣的問題,如果把上述中的test2方法中的finally語句塊中加上return,編譯器就會提示警告:finally block does not complete normally
public int test2(){ int divider=10; int result=100; try{ while(divider>-1){ divider--; result=result+100/divider; } return result; }catch(Exception e){ e.printStackTrace(); System.out.println("異常拋出了!!"); return result=999; }finally{ System.out.println("這是finally,哈哈哈!!"); System.out.println("result的值為:"+result); return result;//編譯器警告 } }
分析問題: finally塊中的return語句可能會覆蓋try塊、catch塊中的return語句;如果finally塊中包含了return語句,即使前面的catch塊重新拋出了異常,則調用該方法的語句也不會獲得catch塊重新拋出的異常,而是會得到finally塊的返回值,并且不會捕獲異常。
解決問題:面對上述情況,其實更合理的做法是,既不在try block內部中使用return語句,也不在finally內部使用 return語句,而應該在 finally 語句之后使用return來表示函數的結束和返回。如:
總結:
1、不管有木有出現異常或者try和catch中有返回值return,finally塊中代碼都會執行;
2、finally中最好不要包含return,否則程序會提前退出,返回會覆蓋try或catch中保存的返回值。
3、 e.printStackTrace()可以輸出異常信息。
4、 return值為-1為拋出異常的習慣寫法。
5、 如果方法中try,catch,finally中沒有返回語句,則會調用這三個語句塊之外的return結果。
6、 finally 在try中的return之后 在返回主調函數之前執行。
三、throw和throws關鍵字
java中的異常拋出通常使用throw和throws關鍵字來實現。
throw ----將產生的異常拋出,是拋出異常的一個動作。
一般會用于程序出現某種邏輯時程序員主動拋出某種特定類型的異常。如:
語法:throw (異常對象),如:
public static void main(String[] args) { String s = "abc"; if(s.equals("abc")) { throw new NumberFormatException(); } else { System.out.println(s); } //function(); }
運行結果:
Exception in thread "main" java.lang.NumberFormatException at test.ExceptionTest.main(ExceptionTest.java:67)
throws----聲明將要拋出何種類型的異常(聲明)。
語法格式:
public void 方法名(參數列表) throws 異常列表{ //調用會拋出異常的方法或者: throw new Exception(); }
當某個方法可能會拋出某種異常時用于throws 聲明可能拋出的異常,然后交給上層調用它的方法程序處理。如:
public static void function() throws NumberFormatException{ String s = "abc"; System.out.println(Double.parseDouble(s)); } public static void main(String[] args) { try { function(); } catch (NumberFormatException e) { System.err.println("非數據類型不能轉換。"); //e.printStackTrace(); } }
throw與throws的比較
1、throws出現在方法函數頭;而throw出現在函數體。
2、throws表示出現異常的一種可能性,并不一定會發生這些異常;throw則是拋出了異常,執行throw則一定拋出了某種異常對象。
3、兩者都是消極處理異常的方式(這里的消極并不是說這種方式不好),只是拋出或者可能拋出異常,但是不會由函數去處理異常,真正的處理異常由函數的上層調用處理。
來看個例子:
throws e1,e2,e3只是告訴程序這個方法可能會拋出這些異常,方法的調用者可能要處理這些異常,而這些異常e1,e2,e3可能是該函數體產生的。
throw則是明確了這個地方要拋出這個異常。如:
void doA(int a) throws (Exception1,Exception2,Exception3){ try{ ...... }catch(Exception1 e){ throw e; }catch(Exception2 e){ System.out.println("出錯了!"); } if(a!=b) throw new Exception3("自定義異常"); }
分析:
1、代碼塊中可能會產生3個異常,(Exception1,Exception2,Exception3)。
2、如果產生Exception1異常,則捕獲之后再拋出,由該方法的調用者去處理。
3、如果產生Exception2異常,則該方法自己處理了(即System.out.println("出錯了!");)。所以該方法就不會再向外拋出Exception2異常了,void doA() throws Exception1,Exception3 里面的Exception2也就不用寫了。因為已經用try-catch語句捕獲并處理了。
4、Exception3異常是該方法的某段邏輯出錯,程序員自己做了處理,在該段邏輯錯誤的情況下拋出異常Exception3,則該方法的調用者也要處理此異常。這里用到了自定義異常,該異常下面會由解釋。
使用throw和throws關鍵字需要注意以下幾點:
1.throws的異常列表可以是拋出一條異常,也可以是拋出多條異常,每個類型的異常中間用逗號隔開
2.方法體中調用會拋出異常的方法或者是先拋出一個異常:用throw new Exception() throw寫在方法體里,表示“拋出異常”這個動作。
3.如果某個方法調用了拋出異常的方法,那么必須添加try catch語句去嘗試捕獲這種異常, 或者添加聲明,將異常拋出給更上一層的調用者進行處理
自定義異常
為什么要使用自定義異常,有什么好處?
1、我們在工作的時候,項目是分模塊或者分功能開發的 ,基本不會你一個人開發一整個項目,使用自定義異常類就統一了對外異常展示的方式。
2、有時候我們遇到某些校驗或者問題時,需要直接結束掉當前的請求,這時便可以通過拋出自定義異常來結束,如果你項目中使用了SpringMVC比較新的版本的話有控制器增強,可以通過@ControllerAdvice注解寫一個控制器增強類來攔截自定義的異常并響應給前端相應的信息。
3、自定義異常可以在我們項目中某些特殊的業務邏輯時拋出異常,比如"中性".equals(sex),性別等于中性時我們要拋出異常,而Java是不會有這種異常的。系統中有些錯誤是符合Java語法的,但不符合我們項目的業務邏輯。
4、使用自定義異常繼承相關的異常來拋出處理后的異常信息可以隱藏底層的異常,這樣更安全,異常信息也更加的直觀。自定義異常可以拋出我們自己想要拋出的信息,可以通過拋出的信息區分異常發生的位置,根據異常名我們就可以知道哪里有異常,根據異常提示信息進行程序修改。比如空指針異常NullPointException,我們可以拋出信息為“xxx為空”定位異常位置,而不用輸出堆棧信息。
說完了為什么要使用自定義異常,有什么好處,我們再來看看自定義異常的毛病:
我們不可能期待JVM(Java虛擬機)自動拋出一個自定義異常,也不能夠期待JVM會自動處理一個自定義異常。發現異常、拋出異常以及處理異常的工作必須靠編程人員在代碼中利用異常處理機制自己完成。這樣就相應的增加了一些開發成本和工作量,所以項目沒必要的話,也不一定非得要用上自定義異常,要能夠自己去權衡。
最后,我們來看看怎么使用自定義異常:
在 Java 中你可以自定義異常。編寫自己的異常類時需要記住下面的幾點。
所有異常都必須是 Throwable 的子類。
如果希望寫一個檢查性異常類,則需要繼承 Exception 類。
如果你想寫一個運行時異常類,那么需要繼承 RuntimeException 類。
可以像下面這樣定義自己的異常類:
class MyException extends Exception{ }
我們來看一個實例:
package com.hysum.test; public class MyException extends Exception { /** * 錯誤編碼 */ private String errorCode; public MyException(){} /** * 構造一個基本異常. * * @param message * 信息描述 */ public MyException(String message) { super(message); } public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } }
使用自定義異常拋出異常信息:
package com.hysum.test; public class Main { public static void main(String[] args) { // TODO Auto-generated method stub String[] sexs = {"男性","女性","中性"}; for(int i = 0; i < sexs.length; i++){ if("中性".equals(sexs[i])){ try { throw new MyException("不存在中性的人!"); } catch (MyException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ System.out.println(sexs[i]); } } } }
運行結果:
四、java中的異常鏈
異常需要封裝,但是僅僅封裝還是不夠的,還需要傳遞異常。
異常鏈是一種面向對象編程技術,指將捕獲的異常包裝進一個新的異常中并重新拋出的異常處理方式。原異常被保存為新異常的一個屬性(比如cause)。這樣做的意義是一個方法應該拋出定義在相同的抽象層次上的異常,但不會丟棄更低層次的信息。
我可以這樣理解異常鏈:
把捕獲的異常包裝成新的異常,在新異常里添加原始的異常,并將新異常拋出,它們就像是鏈式反應一樣,一個導致(cause)另一個。這樣在最后的頂層拋出的異常信息就包括了最底層的異常信息。
》場景
比如我們的JEE項目一般都又三層:持久層、邏輯層、展現層,持久層負責與數據庫交互,邏輯層負責業務邏輯的實現,展現層負責UI數據的處理。
有這樣一個模塊:用戶第一次訪問的時候,需要持久層從user.xml中讀取數據,如果該文件不存在則提示用戶創建之。
那問題就來了:如果我們直接把持久層的異常FileNotFoundException拋棄掉,邏輯層根本無從得知發生任何事情,也就不能為展現層提供一個友好的處理結果,最終倒霉的就是展現層:沒有辦法提供異常信息,只能告訴用戶“出錯了,我也不知道出了什么錯了”—毫無友好性而言。
正確的做法是先封裝,然后傳遞,過程如下:
1、把FileNotFoundException封裝為MyException。
2、拋出到邏輯層,邏輯層根據異常代碼(或者自定義的異常類型)確定后續處理邏輯,然后拋出到展現層。
3、展現層自行確定展現什么,如果管理員則可以展現低層級的異常,如果是普通用戶則展示封裝后的異常。
示例
package com.hysum.test; public class Main { public void test1() throws RuntimeException{ String[] sexs = {"男性","女性","中性"}; for(int i = 0; i < sexs.length; i++){ if("中性".equals(sexs[i])){ try { throw new MyException("不存在中性的人!"); } catch (MyException e) { // TODO Auto-generated catch block e.printStackTrace(); RuntimeException rte=new RuntimeException(e);//包裝成RuntimeException異常 //rte.initCause(e); throw rte;//拋出包裝后的新的異常 } }else{ System.out.println(sexs[i]); } } } public static void main(String[] args) { // TODO Auto-generated method stub Main m =new Main(); try{ m.test1(); }catch (Exception e){ e.printStackTrace(); e.getCause();//獲得原始異常 } } }
運行結果:
結果分析:我們可以看到控制臺先是輸出了原始異常,這是由e.getCause()輸出的;然后輸出了e.printStackTrace(),在這里可以看到Caused by:原始異常和e.getCause()輸出的一致。這樣就是形成一個異常鏈。
initCause()的作用是包裝原始的異常,當想要知道底層發生了什么異常的時候調用getCause()就能獲得原始異常。
建議
異常需要封裝和傳遞,我們在進行系統開發的時候,不要“吞噬”異常,也不要“赤裸裸”的拋出異常,封裝后在拋出,或者通過異常鏈傳遞,可以達到系統更健壯、友好的目的。
五、結束語
java的異常處理的知識點雜而且理解起來也有點困難,我在這里給大家總結了以下幾點使用java異常處理的時候,良好的編碼習慣:
1、處理運行時異常時,采用邏輯去合理規避同時輔助try-catch處理
2、在多重catch塊后面,可以加一個catch(Exception)來處理可能會被遺漏的異常
3、對于不確定的代碼,也可以加上try-catch,處理潛在的異常
4、盡量去處理異常,切記只是簡單的調用printStackTrace()去打印
5、具體如何處理異常,要根據不同的業務需求和異常類型去決定
6、盡量添加finally語句塊去釋放占用的資源
關于Java中的異常和異常處理知識點就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。