Java高效利用異常處理的技巧總結(jié)
概述
在使用計算機語言進行項目開發(fā)的過程中,即使程序員把代碼寫的盡善盡美,在系統(tǒng)的運行過程中仍然會遇到一些問題,因為很多問題不是靠代碼就能避免的,比如:客戶輸入數(shù)據(jù)的格式問題,輸入的文件是否存在,網(wǎng)絡(luò)是否始終通暢等。
那么在程序執(zhí)行過程中,出現(xiàn)的非正常情況,如果不處理最終會導(dǎo)致JVM的非正常停止,這就是異常。
異常指的并不是語法錯誤和邏輯錯誤,語法錯了編譯不會通過,不會產(chǎn)生字節(jié)碼文件,根本不能運行。而代碼邏輯錯誤,只是沒有得到想要的結(jié)果。
Java中把不同的異常用不同的類表示,一旦發(fā)生某種異常,就創(chuàng)建該異常類型的對象并且拋出(throw),然后程序員可以catch到這個異常對象并處理,如果無法catch到異常對象,那么這個異常對象將會導(dǎo)致程序的終止。
所以在編寫程序時,就應(yīng)該充分考慮到各種可能發(fā)生的異常和錯誤,極力的預(yù)防和避免,實在無法避免的,也要編寫相應(yīng)的代碼進行異常的檢測、以及處理,從而保證代碼的健壯性。
Java異常體系
Error和Exception
Java程序在執(zhí)行過程中所發(fā)生的異常事件可分為兩類:Error和Exception,分別對應(yīng)著java.lang.Error
和java.lang.Exception
兩個類。
Error是Java虛擬機無法解決的嚴(yán)重問題,如JVM系統(tǒng)內(nèi)部錯誤、資源耗盡等嚴(yán)重情況,一般不編寫針對性的代碼進行處理。
Exception是因編程錯誤或偶然的外在因素導(dǎo)致的一般性問題,可以使用針對性的代碼進行處理,使程序繼續(xù)運行,但只要發(fā)生必須處理,否則程序也會終止,例如:
- 空指針訪問
- 試圖讀取不存在的文件
- 網(wǎng)絡(luò)連接中斷
- 數(shù)組角標(biāo)越界
無論是Error還是Exception,還有很多子類,異常的類型非常豐富,當(dāng)代碼運行出現(xiàn)異常時,特別是不熟悉的異常時,可以在API中通過異常的類名去查詢。
提示: 平時說的異常就是指Exception。
Throwable
java.lang.Throwable
類是Error和Exception的父類。只有當(dāng)對象是此類(或其子類之一)的實例時,才能通過Java虛擬機或Java的throw語句拋出,同樣的只有此類或其子類之一才可以是catch子句中的參數(shù)類型。
Throwable中的常用方法:
public void printStackTrace()
:打印異常的詳細信息,包含了異常的類型、原因和出現(xiàn)的位置,在開發(fā)和調(diào)試階段都得使用printStackTrace。public String getMessage()
:獲取發(fā)生異常的原因,提示給用戶的時候就提示錯誤的原因。
常見的錯誤和異常
對于程序出現(xiàn)的異常,一般有兩種,一是遇到錯誤就終止程序的運行,另一種是由程序員在編寫程序時,就考慮到錯誤的檢測、錯誤消息的提示,以及錯誤的處理。
最理想的捕獲異常是在編譯期間,但有的錯誤只有在運行時才會發(fā)生,比如數(shù)組下標(biāo)越界等。因此根據(jù)代碼的執(zhí)行階段,編譯器是否會警示當(dāng)前代碼可能發(fā)生異常,并督促程序員提前編寫處理它的代碼為依據(jù),可以將異常分為編譯時異常和運行時異常。
編譯時異常
編譯時異常(checked
異常、受檢異常):在代碼編譯階段,編譯器就能明確警示當(dāng)前代碼可能發(fā)生但不一定發(fā)生的異常,并明確督促提前編寫處理代碼,如果沒有編寫相應(yīng)的異常處理代碼,則編譯器就直接判定編譯失敗,從而導(dǎo)致程序無法執(zhí)行,通常這類異常的發(fā)生不是由編寫的代碼引起的,或不是靠加簡單判斷可以避免的。
示例:
import org.junit.Test; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class TestCheckedException { @Test public void test01() { Thread.sleep(1000); //休眠1秒,InterruptedException } @Test public void test02(){ Class c = Class.forName("java.lang.String"); //ClassNotFoundException } @Test public void test03() { Connection conn = DriverManager.getConnection("xxx"); //SQLException } @Test public void test04() { FileInputStream fis = new FileInputStream("xxx.txt"); //FileNotFoundException } @Test public void test5() { File file = new File("xxx.txt"); FileInputStream fis = new FileInputStream(file); //FileNotFoundException int b = fis.read(); //IOException while(b != -1){ System.out.print((char)b); b = fis.read(); //IOException } fis.close(); //IOException } }
運行時異常
運行時異常(runtime
異常、unchecked
異常、非受檢異常):在代碼編譯階段,編譯器完全不做任何的檢查,無論該異常是否會發(fā)生,編譯器都不給出任何提示。只有等代碼運行起來并確定發(fā)生了異常才能被發(fā)現(xiàn),通常這類異常是由程序員的代碼編寫不當(dāng)引起的,只要稍加判斷或細心檢查就可以避免。
示例:
import org.junit.Test; import java.util.Scanner; public class TestRuntimeException { @Test public void test01(){ //NullPointerException int[][] arr = new int[3][]; System.out.println(arr[0].length); } @Test public void test02(){ //ClassCastException Object obj = 15; String str = (String) obj; } @Test public void test03(){ //ArrayIndexOutOfBoundsException int[] arr = new int[5]; for (int i = 1; i <= 5; i++) { System.out.println(arr[i]); } } @Test public void test04(){ //InputMismatchException Scanner input = new Scanner(System.in); System.out.print("請輸入一個整數(shù):"); //輸入非整數(shù) int num = input.nextInt(); input.close(); } @Test public void test05(){ int a = 1; int b = 0; //ArithmeticException System.out.println(a/b); } }
Error
最常見的就是VirtualMachineError,有兩個經(jīng)典的子類:StackOverflowError
、OutOfMemoryError
。
示例:
import org.junit.Test; public class TestError { //StackOverflowError @Test public void test01(){ recursion(); } public void recursion(){ //遞歸方法 recursion(); } //OutOfMemoryError @Test public void test02(){ int[] arr = new int[Integer.MAX_VALUE]; //方式一 } @Test public void test03(){ StringBuilder s = new StringBuilder(); //方式二 while(true){ s.append("xxx"); } } }
異常的處理
在編寫程序時,經(jīng)常要在可能出現(xiàn)錯誤的地方加上檢測的代碼,如進行除法運算時,需要檢測分母是否為0等,這樣一來會出現(xiàn)過多的if-else分支,導(dǎo)致程序的代碼加長、臃腫,可讀性差,程序員需要花很大的精力去堵漏洞,因此采用異常處理機制。
Java采用的異常處理機制是將異常處理的程序代碼集中到一起,與正常的代碼分開,使得程序簡潔優(yōu)雅并易于維護。
Java異常處理的方式有兩種,一種是try-catch-finally
,另一種是throws+異常類型
。
捕獲異常
Java提供了異常處理的抓拋模型,之前提到Java程序的執(zhí)行過程中如果出現(xiàn)異常,會生成一個異常類對象,該異常對象將被提交給Java運行時系統(tǒng),這個過程稱為拋出異常。
如果一個方法內(nèi)拋出異常,該異常對象會被拋給調(diào)用者方法中處理,如果異常沒有在調(diào)用者方法中處理,它繼續(xù)被拋給這個調(diào)用方法的上層方法,這個過程將一直繼續(xù)下去,直到異常被處理,這個過程稱為捕獲異常。
如果一個異常回到main方法,并且main方法不處理,則程序終止。
捕獲異常的語法如下:
try{ ...... //可能產(chǎn)生異常的代碼 } catch( 異常類型1 e ){ ...... //當(dāng)產(chǎn)生ExceptionName1類型異常時的處理 } catch( 異常類型2 e ){ ...... //當(dāng)產(chǎn)生ExceptionName2類型異常時的處理 } [finally{ ...... //無論是否發(fā)生異常,都無條件執(zhí)行的代碼 }]
當(dāng)某段代碼可能發(fā)生異常時,不管此異常是編譯時異常還是運行時異常,都可以使用try塊括起來,并在try塊下面編寫catch分支捕獲對應(yīng)的異常對象。
- 如果在程序運行時,try中代碼沒有發(fā)生異常,則catch中代碼不執(zhí)行
- 如果在程序運行時,try中代碼發(fā)生了異常,則根據(jù)異常的類型從上到下選擇第一個匹配的catch分支執(zhí)行,此時try中發(fā)生異常的語句下面的代碼將不執(zhí)行,整個try-catch之后的代碼可以繼續(xù)執(zhí)行
- 如果在程序運行時,try中代碼發(fā)生了異常,但所有的catch分支都無法匹配這個異常,那么JVM將會終止當(dāng)前方法的執(zhí)行,并把異常對象拋給調(diào)用者,如果調(diào)用者也不處理,那么程序就掛了
注意: 當(dāng)有多個catch分支時,且多個異常類型有父子類關(guān)系,必須保證小的子異常類型在上,大的父異常類型在下。
示例:
import java.util.InputMismatchException; import java.util.Scanner; public class TestTryCatch { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("請輸入第一個整數(shù):"); int a = input.nextInt(); System.out.print("請輸入第二個整數(shù):"); int b = input.nextInt(); int result = a/b; System.out.println(a + "/" + b +"=" + result); } catch (InputMismatchException e) { System.out.println("數(shù)字格式不正確,請輸入兩個整數(shù)"); }catch (ArithmeticException e){ System.out.println("第二個整數(shù)不能為0"); } finally { System.out.println("程序結(jié)束,釋放資源"); input.close(); } } }
catch捕獲異常的信息,與其它對象一樣,可以訪問一個異常對象的成員變量或調(diào)用它的方法,為此Throwable類中定義了一些查看方法,比如有:
public String getMessage()
:獲取異常的描述信息,返回字符串public void printStackTrace()
:打印異常的跟蹤棧信息并輸出到控制臺,包含異常的類型、原因和出現(xiàn)的位置,在開發(fā)和調(diào)試階段都得使用printStackTrace
示例:
@Test public void test1(){ FileInputStream fis = null; try{ File file = new File("xxx.txt"); fis = new FileInputStream(file); //FileNotFoundException int b = fis.read(); //IOException while(b != -1){ System.out.print((char)b); b = fis.read(); //IOException } }catch(IOException e){ e.printStackTrace(); }finally{ try { if(fis != null) fis.close(); //IOException } catch (IOException e) { e.printStackTrace(); } } }
如果多個catch分支的異常處理代碼一致,在JDK1.7之后還支持以下寫法:
try{
可能發(fā)生異常的代碼
}catch(異常類型1 | 異常類型2 e){
處理異常的代碼1
}catch(異常類型3 e){
處理異常的代碼2
}
....
最后,這些異常都是運行時異常類或它的子類,這些異常類的特點是即使沒有使用try-catch,Java也能捕獲并且編譯通過。如果拋出的異常是IOException等類型的非運行時異常,則必須捕獲,否則編譯錯誤,所以必須處理編譯時異常,將異常進行捕獲轉(zhuǎn)化為運行時異常。
聲明拋出異常類型
如果一個方法可能生成某種異常,但并不能確定如何處理這種異常,則此方法應(yīng)顯示的聲明拋出異常,表明該方法將不對這些異常進行處理,而由該方法的調(diào)用者負責(zé)處理。在方法聲明中用throws語句可以聲明拋出異常的列表,throws后面的異常類型可以是方法中產(chǎn)生的異常類型,也可以是它的父類。
throws聲明異常格式如下:
修飾符 返回值類型 方法名(參數(shù)) throws 異常類名1,異常類名2…{ }
在throws后面可以寫多個異常類型,用逗號隔開。
示例:
public void readFile(String file) throws FileNotFoundException { ... //讀文件的操作可能產(chǎn)生FileNotFoundException類型的異常 FileInputStream fis = new FileInputStream(file); ... }
throws編譯時異常:
如果在編寫方法體代碼時,某代碼可能發(fā)生某個編譯時異常,不處理編譯不通過,但在當(dāng)前方法體中可能不適合處理或無法給出合理的處理方式,就可以通過throws在方法簽名中聲明該方法可能會發(fā)生異常,需要調(diào)用者處理。
示例:
public class TestThrowsCheckedException { public static void main(String[] args) { System.out.println("xxxxxx"); try { afterClass(); //換到這里處理異常 } catch (InterruptedException e) { e.printStackTrace(); System.out.println("XXXXXX"); } System.out.println("......"); } public static void afterClass() throws InterruptedException { for(int i=10; i>=1; i--){ Thread.sleep(1000); //本來應(yīng)該在這里處理異常 System.out.println("xxxxxx" + i); } } }
throws運行時異常:
throws后面也可以寫運行時異常類型,只是運行時異常類型寫或不寫對于編譯器和程序執(zhí)行來說都沒有任何區(qū)別,如果寫了,唯一的區(qū)別就是調(diào)用者調(diào)用該方法后使用try-catch結(jié)構(gòu)時,idea可以獲得更多的信息,需要添加哪種catch分支。
示例:
import java.util.InputMismatchException; import java.util.Scanner; public class TestThrowsRuntimeException { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("請輸入第一個整數(shù):"); int a = input.nextInt(); System.out.print("請輸入第二個整數(shù):"); int b = input.nextInt(); int result = divide(a,b); System.out.println(a + "/" + b +"=" + result); } catch (ArithmeticException | InputMismatchException e) { e.printStackTrace(); } finally { input.close(); } } public static int divide(int a, int b)throws ArithmeticException{ return a/b; } }
方法重寫對于throws的要求:
方法重寫時,對于方法簽名是有嚴(yán)格要求的,比如:
- 方法名必須相同
- 形參列表必須相同
- 返回值類型:基本數(shù)據(jù)類型和void必須相同;引用數(shù)據(jù)類型要<=
- 權(quán)限修飾符:>=,而且要求父類被重寫方法在子類中是可見的
- 不能是static、final修飾的方法
此外,對于throws異常列表要求:
- 如果父類被重寫方法的方法簽名后沒有throws編譯時異常類型,那么重寫方法時,方法簽名后面也不能出現(xiàn)throws編譯時異常類型
- 如果父類被重寫方法的方法簽名后面有
throws 編譯時異常類型
,那么重寫方法時throws的編譯時異常類型必須<=被重寫方法throws的編譯時異常類型,或者不throws編譯時異常 - 方法重寫對于
throws 運行時異常類型
沒有要求
示例:
import java.io.IOException; class Father{ public void method()throws Exception{ System.out.println("Father.method"); } } class Son extends Father{ @Override public void method() throws IOException,ClassCastException { System.out.println("Son.method"); } }
手動拋出異常對象:throw
Java中異常對象的生成有兩種方式:
- 由虛擬機自動生成:程序運行過程中,虛擬機檢測到程序發(fā)生了問題,針對當(dāng)前代碼就會在后臺自動創(chuàng)建一個對應(yīng)異常類的實例對象并拋出
- 由開發(fā)人員手動創(chuàng)建:
new 異常類型([實參列表])
,如果創(chuàng)建好的異常對象不拋出對程序沒有任何影響,和創(chuàng)建一個普通對象一樣,但一旦throw拋出,就會對程序運行產(chǎn)生影響了
語法格式
使用格式如下:
throw new 異常類名(參數(shù));
throw語句拋出的異常對象和JVM自動創(chuàng)建和拋出的異常對象一樣:
- 如果是編譯時異常類型的對象,同樣需要使用throws或try-catch處理,否則編譯不通過
- 如果是運行時異常類型的對象,編譯器不提示
- 可以拋出的異常必須是Throwable或其子類的實例
//編譯時會產(chǎn)生語法錯誤 throw new String("want to throw");
注意事項
無論是編譯時異常類型的對象還是運行時異常類型的對象,如果沒有被try-catch合理的處理,都會導(dǎo)致程序的崩潰。
throw語句會導(dǎo)致程序執(zhí)行流程被改變,throw語句是明確拋出一個異常對象,因此它下面的代碼將不會執(zhí)行。
如果當(dāng)前方法沒有try-catch處理這個異常對象,throw語句就會代替return語句提前終止當(dāng)前方法的執(zhí)行,并返回一個異常對象給調(diào)用者。
示例:
public class TestThrow { public static void main(String[] args) { try { System.out.println(max(4,2,31,1)); } catch (Exception e) { e.printStackTrace(); } try { System.out.println(max(4)); } catch (Exception e) { e.printStackTrace(); } try { System.out.println(max()); } catch (Exception e) { e.printStackTrace(); } } public static int max(int... nums){ if(nums == null || nums.length==0){ throw new IllegalArgumentException("沒有傳入任何整數(shù),無法獲取最大值"); } int max = nums[0]; for (int i = 1; i < nums.length; i++) { if(nums[i] > max){ max = nums[i]; } } return max; } }
自定義異常
為什么需要自定義異常類
Java中不同的異常類分別表示著某一種具體的異常情況,那么在開發(fā)中總是有些異常情況是核心類庫中沒有定義好的,此時就需要根據(jù)自己的業(yè)務(wù)異常情況來定義異常類。
如何自定義異常類
要繼承一個異常類型。
- 自定義一個編譯時異常類型:自定義類繼承
java.lang.Exception
- 自定義一個運行時異常類型:自定義類繼承
java.lang.RuntimeException
建議提供至少兩個構(gòu)造器,一個是無參構(gòu)造器一個是(String message)構(gòu)造器。
自定義異常需要提供serialVersionUID
。
注意:
自定義的異常只能通過throw拋出。
自定義異常最重要的是異常類的名字和message屬性,當(dāng)異常出現(xiàn)時可以根據(jù)名字判斷異常類型。
自定義異常對象只能手動拋出,拋出后由try-catch處理,也可以甩鍋throws給調(diào)用者處理。
示例:
//自定義異常類 class MyException extends Exception { static final long serialVersionUID = 23423423435L; private int idnumber; public MyException(String message, int id) { super(message); this.idnumber = id; } public int getId() { return idnumber; } } //測試 public class MyExpTest { public void regist(int num) throws MyException { if (num < 0) throw new MyException("人數(shù)為負值,不合理", 3); else System.out.println("登記人數(shù)" + num); } public void manager() { try { regist(100); } catch (MyException e) { System.out.print("登記失敗,出錯種類" + e.getId()); } System.out.print("本次登記操作結(jié)束"); } public static void main(String args[]) { MyExpTest t = new MyExpTest(); t.manager(); } }
小結(jié)
異常處理的5個關(guān)鍵字:
到此這篇關(guān)于Java高效利用異常處理的技巧總結(jié)的文章就介紹到這了,更多相關(guān)Java異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java ArrayList和Vector的區(qū)別詳解
這篇文章主要介紹了java ArrayList和Vector的區(qū)別詳解的相關(guān)資料,并附簡單實例代碼,需要的朋友可以參考下2016-11-11SpringBoot2 整合MinIO中間件實現(xiàn)文件便捷管理功能
這篇文章主要介紹了SpringBoot2 整合MinIO中間件,實現(xiàn)文件便捷管理,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07在Java的Hibernate框架中使用SQL語句的簡單介紹
這篇文章主要介紹了在Java的Hibernate框架中使用SQL語句的方法,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2016-01-01Java實現(xiàn)學(xué)生管理系統(tǒng)(控制臺版本)
這篇文章主要為大家詳細介紹了如何利用Java語言實現(xiàn)控制臺版本的學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06java接口類中的@selectProvider接口的使用及說明
這篇文章主要介紹了java接口類中的@selectProvider接口的使用及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08