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