全面了解java異常
異常的概念
異常,在程序中的意思是:程序在執(zhí)行過程中,出現(xiàn)的非正常的情況,最終會導致JVM的非正常停止。
Java的異常機制主要依賴于try、catch、finally、throw和throws五個關鍵字
其中try關鍵字后面緊跟著一個花括號括起來的代碼塊,它里面放置可能會引發(fā)異常的代碼塊。catch后面對應異常類型和一個代碼塊,用于表明該catch塊用于處理這種異常類型的代碼塊,多個catch塊后面還可以跟一個finally塊,用于回收在try塊里打開的物理資源,異常機制會保證finally塊一定被執(zhí)行。
throws關鍵字主要在方法簽名中使用,用于聲明該方法可能拋出的異常;
而throw用于拋出一個實際的異常,throw可以單獨作為語句使用,拋出一個具體的異常對象。
在Java等面向對象的編程語言中,異常本身是一個類,產生異常就是創(chuàng)建并拋出了一個異常對象。Java處理異常的方式是中斷處理。異常指的并不是語法錯誤,語法錯了編譯不能通過,不會產生字節(jié)碼文件,根本不能運行。
異常體系
Error:嚴重錯誤Error,無法通過處理的錯誤,只能事先避免,比如內存溢出。
Exception:表示異常,異常產生后程序員可以通過代碼的方式糾正,使程序繼續(xù)運行,是必須要處理的
異常的分類
我們平常說的異常就是指Exception,因為這類異常一旦出現(xiàn),我們就要對代碼進行更正,修復程序。
異常(Exception)的分類:根據在編譯時期還是運行時期去檢查異常。
編譯時期異常:checked異常。在編譯時期就會檢查,如果沒有處理異常,則編譯失敗。(如日期格式化異常)運行時期異常:
runtime異常。在運行時期,檢查異常。在編譯時期,運行期異常不會被編譯器檢測(不報錯)。(如數學異常)
異常產生的過程分析
1.JVM(java virtual machine)會檢測出程序出現(xiàn)的異常,JVM會做兩件事情。
1).JVM會根據異常產生的原因創(chuàng)建一個異常對象,這個異常對象包含了異常產生的(內容,原因,位置)。
2).在方法中,沒有異常處理的邏輯(try-catch),那么JVM就會把異常對象拋出給方法的調用者main方法來處理這個異常.
2.MAIN方法接受到這個異常對象,MAIN方法也沒有異常的處理邏輯,繼續(xù)把對象拋出給MAIN方法的調用者JVM處理(MAIN方法把異常對象拋出給JVM)
3.JVM接受到這個異常對象,做了兩件事情。
1).把異常對象(內容,原因,位置)以紅色的字體打印在控制臺
2).JVM會終止當前正在執(zhí)行的java程序 à中斷處理
異常的處理
Java異常處理的五個關鍵字:try、catch、finally、throw、throws。
拋出異常throw
在編寫程序時,我們必須要考慮程序出現(xiàn)問題的情況。比如,在定義方法時,方法需要接受參數。那么,當調用方法使用接受到的參數時,首先需要先對參數數據進行合法的判斷,數據若不合法,就應該告訴調用者,傳遞合法的數據進來。這時需要使用拋出異常的方式來告訴調用者。
在java中,提供了一個throw關鍵字,它用來拋出一個指定的異常對象。那么,拋出一個異常具體如何操作呢?
1.創(chuàng)建一個異常對象。封裝一些提示信息(信息可以自己編寫)。
2.需要將這個異常對象告知給調用者。怎么告知呢?怎么將這個異常對象傳遞到調用者處呢?通過關鍵字throw就可以完成。
throw用在方法內,用來拋出一個異常對象,將這個異常對象傳遞到調用者處,并結束當前方法的執(zhí)行。
格式
throw new 異常類名(參數); //例: public class ThrowDemo{ public static void main(String[]args){ //創(chuàng)建一個數組 int[]arr={9,5,2,7}; //根據索引找對應元素 int index = 4; int element = getElement(arr,index); System.out.println(element); System.out.println("over!"); } public static int getElement(int arr[],int index){ //判斷索引是否越界 if(index<0||index>arr.length-1){ /* *判斷條件如果滿足,當執(zhí)行完throw拋出異常對象后,方法已經無法繼續(xù)運算. *這時就會結束當前方法的執(zhí)行,并將異常告知給調用者,這時就需要通過異常來解決. */ throw new ArrayIndexOutOfBoundsException("哥們,角標越界了!!!"); } int element=arr[index]; return element; } }
注意:如果產生了問題,我們就會throw將問題描述類即異常進行拋出,也就是將問題返回給該方法的調用者。
那么對于調用者來說,該怎么處理呢?一種是進行捕獲處理,另一種就是繼續(xù)講問題聲明出去,使用throws聲明處理。
聲明異常throws
聲明異常:將問題標識出來,報告給調用者。如果方法內通過throw拋出了編譯時異常,而沒有捕獲處理(稍后講解該方式),那么必須通過throws進行聲明,讓調用者去處理。
關鍵字throws運用于方法聲明之上,用于表示當前方法不處理異常,而是提醒該方法的調用者來處理異常(拋出異常)
聲明異常格式:
修飾符 返回值類型 方法名 (參數列表) throws 異常類名1,異常類名2... { } //示例代碼 public class ThrowsDemo{ public static void main(String[]args) throws FileNotFoundException { read("a.txt"); } //定義功能時有問題發(fā)生需要報告給調用者.可以通過在方法上使用throws關鍵字進行聲明 public static void read(String path) throws FileNotFoundException{ if(!path.equals("a.txt")){ //如果不是a.txt就認為文件不存在是一個異常拋出問題! throw new FileNotFoundException("文件不存在"); } } }
throws用于進行異常類的聲明,若該方法可能有多種異常情況產生,那么在throws后面可以寫多個異常類,用逗號隔開。
public class ThrowsDemo2 { public static void main(String[]args) throwsIOException { read("a.txt"); } //定義功能時有問題發(fā)生需要報告給調用者.可以通過在方法上使用throws關鍵字進行聲明 public static void read(String path) throws FileNotFoundException , IOException { if(!path.equals("a.txt")) { //如果不是a.txt就認為文件不存在是一個異常拋出問題! throw new FileNotFoundException("文件不存在"); } if(!path.equals("b.txt")){ throw new IOException(); } } }
捕獲異常try…catch
如果異常出現(xiàn)的話,會立刻終止程序,所以我們得處理異常:
1.該方法不處理,而是聲明拋出,由該方法的調用者來處理(throws)。
2.在方法中使用try-catch的語句塊來處理異常。
try-catch的方式就是捕獲異常。
捕獲異常:Java中對異常有針對性的語句進行捕獲,可以對出現(xiàn)的異常進行指定方式的處理。
捕獲異常語法
try { 編寫可能會出現(xiàn)異常的代碼 }catch(異常類型 e){ 處理異常的代碼 //記錄日志/打印異常信息/繼續(xù)拋出異常 }
try:該代碼塊中編寫可能產生異常的代碼。
catch:用來進行某種異常的捕獲,實現(xiàn)對捕獲到的異常進行處理。
注意:
try和catch都不能單獨使用,必須連用。
try里面還可以有try…catch
示例代碼:
public class TryCatchDemo { public static void main (String[]args) { try {//當產生異常時,必須有處理方式,要么捕獲要么聲明 read("a.txt"); }catch(FileNotFoundException e){//try中拋出什么異常,括號中就定義什么類型 System.out.println(e); } System.out.println("OVER!"); } //定義功能時有問題發(fā)生需要報告給調用者.可以通過在方法上使用throws關鍵字進行聲明 public static void read (String path) throws FileNotFoundException { if(!path.equals("a.txt")){ //如果不是a.txt就認為文件不存在是一個異常拋出問題! throw new FileNotFoundException("文件不存在"); } } }
如何獲取異常信息:
Throwable類中定義了一些查看方法:
public String getMessage():獲取異常的描述信息,原因(提示給用戶的時候,就提示錯誤原因。
public String toString():獲取異常的類型和異常描述信息(不用)。
public void printStackTrace():打印異常的跟蹤棧信息并輸出到控制臺。包含了異常的類型,異常的原因,還包括異常出現(xiàn)的位置,在開發(fā)和調試階段,都得使用printStackTrace。
示例代碼:
public static void main(String[]args){ try { String str=null; System.out.println(str.length()); }catch(Exceptione){ e.printStackTrace();//打印異常的堆棧追蹤信息 } System.out.println("OVER!"); } //運行結果: java.lang.NullPointerException at com.langsin.exception.ExceptionDemo.main(ExceptionDemo.java:7) OVER!
finally代碼塊
finally:有一些特定的代碼無論異常是否發(fā)生,都需要執(zhí)行。另外,因為異常會引發(fā)程序跳轉,導致有些語句執(zhí)行不到。而finally就是解決這個問題的,在finally代碼塊中存放的代碼都是一定會被執(zhí)行的。
什么時候的代碼必須最終執(zhí)行?
當我們在try語句塊中打開了一些物理資源(磁盤文件/網絡連接/數據庫連接等),我們都得在使用完之后,最終關閉打開的資源。
注意:finally不能單獨使用。
比如在我們之后學習的IO流中,當打開了一個關聯(lián)文件的資源,最后程序不管結果如何,都需要把這個資源關閉掉。
try catch 語句中有return 的各類情況
首先給出一道題目:
下面代碼的運行結果為?
public class test { public int add(int a,int b) { try { return a+b; }catch(Exception e){ System.out.println("catch語句塊"); }finally { System.out.println("finally語句塊"); } return 0; } public static void main(String[] args) { test t=new test(); System.out.println("和是"+t.add(9, 34)); } }
A、catch語句塊 和是43
B、編譯異常
C、finally語句塊 和是43
D、和是43 finally語句塊
正確答案:C
由于學習過編譯原理的課程,知道了System.out.println中要執(zhí)行add()方法與前面的“和是”字符串拼接后才會輸出,因此首先執(zhí)行add()方法。
add()方法中try語句塊中有return語句,那么是否執(zhí)行完try語句塊就直接退出方法了呢?
上述代碼在Eclipse下運行此代碼結果為:
看來盡管try語句塊中有return,還是會執(zhí)行finally語句塊。
看到了合理的解釋是,在try中執(zhí)行到return語句時,不會真正的return,即只是會計算return中的表達式(本題為執(zhí)行a+b),之后將結果保存在一個臨時棧中,接著執(zhí)行finally中的語句,最后才會從臨時棧中取出之前的結果返回。
下面我們在這道題的finally語句中加入這么一行代碼:
a=1;
public class test { public int add(int a,int b) { try { return a+b; }catch(Exception e){ System.out.println("catch語句塊"); }finally { System.out.println("finally語句塊"); a=1; } return 0; } public static void main(String[] args) { test t=new test(); System.out.println("和是"+t.add(9, 34)); } }
下面是運行結果截圖:
從結果看出來結果并沒有發(fā)生改變,這也驗證了finally中的語句不會影響到臨時棧中的值,即在執(zhí)行finally之前,臨時棧中的值已經確定為43了,執(zhí)行finally語句將a的值變?yōu)?,對結果沒有產生影響,執(zhí)行完finally后的輸出結果仍為43.
finally塊中的內容會先于try中的return語句執(zhí)行,如果finall語句塊中也有return語句的話,那么直接從finally中返回了,這也是不建議在finally中return的原因。下面來看這幾種情況。
情況一(try中有return,finally中沒有return):
public class TryTest{ public static void main(String[] args){ System.out.println(test()); } private static int test(){ int num = 10; try{ System.out.println("try"); return num += 80; }catch(Exception e){ System.out.println("error"); }finally{ if (num > 20){ System.out.println("num>20 : " + num); } System.out.println("finally"); } return num; } }
輸出結果如下:
try
num>20 : 90
finally
90
分析:顯然“return num += 80”被拆分成了“num = num+80”和“return num”兩個語句,線執(zhí)行try中的“num = num+80”語句,將其保存起來,在try中的”return num“執(zhí)行前,先將finally中的語句執(zhí)行完,而后再將90返回。
情況二(try和finally中均有return):
public class TryTest{ public static void main(String[] args){ System.out.println(test()); } private static int test(){ int num = 10; try{ System.out.println("try"); return num += 80; }catch(Exception e){ System.out.println("error"); }finally{ if (num > 20){ System.out.println("num>20 : " + num); } System.out.println("finally"); num = 100; return num; } } }
輸出結果如下:
try
num>20 : 90
finally
100
分析:try中的return語句同樣被拆分了,finally中的return語句先于try中的return語句執(zhí)行,因而try中的return被”覆蓋“掉了,不再執(zhí)行。
情況三(finally中改變返回值num):
public class TryTest{ public static void main(String[] args){ System.out.println(test()); } private static int test(){ int num = 10; try{ System.out.println("try"); return num; }catch(Exception e){ System.out.println("error"); }finally{ if (num > 20){ System.out.println("num>20 : " + num); } System.out.println("finally"); num = 100; } return num; } }
輸出結果如下:
try
finally
10
分析:雖然在finally中改變了返回值num,但因為finally中沒有return該num的值,因此在執(zhí)行完finally中的語句后,test()函數會得到try中返回的num的值,而try中的num的值依然是程序進入finally代碼塊前保留下來的值,因此得到的返回值為10。
但是我們來看下面的情況(將num的值包裝在Num類中):
public class TryTest{ public static void main(String[] args){ System.out.println(test().num); } private static Num test(){ Num number = new Num(); try{ System.out.println("try"); return number; }catch(Exception e){ System.out.println("error"); }finally{ if (number.num > 20){ System.out.println("number.num>20 : " + number.num); } System.out.println("finally"); number.num = 100; } return number; } } class Num{ public int num = 10; }
輸出結果如下:
try
finally
100
從結果中可以看出,同樣是在finally中改變了返回值num的值,在情況三中,并沒有被try中的return返回(test()方法得到的不是100),但在這里卻被try中的return語句返回了。
對以上情況的分析,需要深入JVM虛擬機中程序執(zhí)行exection_table中的字節(jié)碼指令時操作棧的的操作情況,可以參考htp://chabaoo.cn/kf/201010/76754.html)這篇文章,也可以參考《深入Java虛擬機:JVM高級特性與最佳實踐》第6章中對屬性表集合的講解部分。
對于含有return語句的情況,這里我們可以簡單地總結如下:
try語句在返回前,將其他所有的操作執(zhí)行完,保留好要返回的值,而后轉入執(zhí)行finally中的語句,而后分為以下三種情況:
- 情況一:如果finally中有return語句,則會將try中的return語句”覆蓋“掉,直接執(zhí)行finally中的return語句,得到返回值,這樣便無法得到try之前保留好的返回值。
- 情況二:如果finally中沒有return語句,也沒有改變要返回值,則執(zhí)行完finally中的語句后,會接著執(zhí)行try中的return語句,返回之前保留的值。
- 情況三:如果finally中沒有return語句,但是改變了要返回的值,這里有點類似與引用傳遞和值傳遞的區(qū)別,分以下兩種情況,:
1)如果return的數據是基本數據類型或文本字符串,則在finally中對該基本數據的改變不起作用,try中的return語句依然會返回進入finally塊之前保留的值。
2)如果return的數據是引用數據類型,而在finally中對該引用數據類型的屬性值的改變起作用,try中的return語句返回的就是在finally中改變后的該屬性的值。
異常注意事項
多個異常使用捕獲又該如何處理呢?
1.多個異常分別處理。
2.多個異常一次捕獲,多次處理。
3.多個異常一次捕獲一次處理。
一般我們是使用一次捕獲多次處理方式,格式如下:
try{ //編寫可能出現(xiàn)異常的代碼 }catch(異常類型A e){//當try中出現(xiàn)A類型異常,就用該catch來捕獲 //處理異常的代碼 //記錄日志/打印異常信息/繼續(xù)拋出異常 }catch(異常類型B e){//當try中出現(xiàn)B類型異常,就用該catch來捕獲 //處理異常的代碼//記錄日志/打印異常信息/繼續(xù)拋出異常 }
注意:這種異常處理方式,要求多個catch中的異常不能相同,并且若catch中的多個異常之間有子父類異常的關系,那么子類異常要求在上面的catch處理,父類異常在下面的catch處理。
運行時異常被拋出可以不處理。即不捕獲也不聲明拋出。
如果finally有return語句,永遠返回finally中的結果,避免該情況。
如果父類拋出了多個異常,子類重寫父類方法時,拋出和父類相同的異常或者是父類異常的子類或者不拋出異常
父類方法沒有拋出異常,子類重寫父類該方法時也不可拋出異常。此時子類產生該異常,只能捕獲處理,不能聲明拋出。
自定義異常
概述
為什么需要自定義異常類:
我們說了Java中不同的異常類,分別表示著某一種具體的異常情況,那么在開發(fā)中總是有些異常情況是SUN沒有定義好的,此時我們根據自己業(yè)務的異常情況來定義異常類。例如年齡負數問題,考試成績負數問題等等。
在上述代碼中,發(fā)現(xiàn)這些異常都是JDK內部定義好的,但是實際開發(fā)中也會出現(xiàn)很多異常,這些異常很可能在JDK中沒有定義過,例如年齡負數問題,考試成績負數問題.那么能不能自己定義異常呢?
在開發(fā)中根據自己業(yè)務的異常情況來定義異常類.比如:自定義一個業(yè)務邏輯異常:RegisterException。一個注冊異常類。
異常類如何定義:
1.自定義一個編譯期異常:自定義類并繼承于java.lang.Exception。
2.自定義一個運行時期的異常類:自定義類并繼承于java.lang.RuntimeException。
自定義異常演示
要求:我們模擬注冊操作,如果用戶名已存在,則拋出異常并提示:親,該用戶名已經被注冊。
首先定義一個注冊異常類RegisterException:
public class RegisterException extends Exception{ /** *空參構造 */ public RegisterException (){ } /** * *@parammessage異常提示*/ public RegisterException(String message){ super (message); } }
模擬登陸操作,使用數組模擬數據庫中存儲的數據,并提供當前注冊賬號是否存在方法用于判斷。
public class Demo { private static String[]names={"lyan","zhyuqi","xdongdong"}; public static void main(String[]args) { try{ //可能出現(xiàn)異常的代碼 checkUsername("zhyuqi"); System.out.println("用戶名可用!");//沒有異常則用戶名沒有重復,可用 }catch(RegisterException e){ //處理異常 e.printStackTrace(); } } //因為RegisterException是編譯期異常,又想調用者去處理,所以在方法上聲明該異常 public static boolean checkUsername(String uname) throws RegisterException{for(Stringname:names){ if(name.equals(uname)){//如果名字在數組里面就拋出注冊異常 throw new RegisterException("親"+uname+"已經被注冊了!"); } } return true; } }
到此這篇關于全面了解java異常的文章就介紹到這了,更多相關java異常內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在springboot3微項目中如何用idea批量創(chuàng)建單元測試邏輯
這篇文章主要介紹了在SpringBoot3項目中使用IntelliJIDEA批量創(chuàng)建單元測試包括準備工作(確保項目配置正確,添加測試依賴),使用IntelliJIDEA創(chuàng)建測試,感興趣的朋友一起看看吧2024-10-10springboot -sse -flux 服務器推送消息的方法
這篇文章主要介紹了springboot -sse -flux 服務器推送消息的相關知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-11-11SpringBoot Jackson日期格式化統(tǒng)一配置的實現(xiàn)
Spring項目中經常需要配置日期時間格式格式,本文主要介紹了SpringBoot Jackson日期格式化統(tǒng)一配置的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-08-08SpringBoot使用AOP實現(xiàn)防重復提交功能
這篇文章主要為大家詳細介紹了SpringBoot如何使用AOP實現(xiàn)防重復提交功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-03-03maven中profile動態(tài)打包不同環(huán)境配置文件的實現(xiàn)
開發(fā)項目時會遇到這個問題:開發(fā)環(huán)境,測試環(huán)境,生產環(huán)境的配置文件不同, 打包時經常要手動更改配置文件,本文就來介紹一下maven中profile動態(tài)打包不同環(huán)境配置文件的實現(xiàn),感興趣的可以了解一下2023-10-10BufferedInputStream(緩沖輸入流)詳解_動力節(jié)點Java學院整理
這篇文章主要為大家詳細介紹了BufferedInputStream緩沖輸入流的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05