在本教程中,您將借助示例學(xué)習(xí)如何用Java處理異常。為了處理異常,我們將使用try ... catch ... finally塊。
在上一教程中,我們了解了異常。異常是程序執(zhí)行期間發(fā)生的意外事件。
在Java中,我們使用異常處理程序組件try,catch和finally塊來處理異常。
為了捕獲和處理異常,我們將try...catch...finally代碼塊放置在可能產(chǎn)生異常的代碼周圍。finally塊是可選的。
try...catch...finally的語法為:
try { // 代碼 } catch (ExceptionType e) { // 捕獲塊 } finally { //finally塊 }
可能會(huì)生成異常的代碼放在try塊中。
每個(gè)try塊后面應(yīng)緊跟著catch 或 finally塊。發(fā)生異常時(shí),它會(huì)被catch緊隨其后的塊捕獲。
catch塊不能單獨(dú)使用,必須緊隨try塊。
class Main { public static void main(String[] args) { try { int divideByZero = 5 / 0; System.out.println("try塊中的其余代碼"); } catch (ArithmeticException e) { System.out.println("ArithmeticException => " + e.getMessage()); } } }
輸出結(jié)果
ArithmeticException => / by zero
在這個(gè)實(shí)例中
我們?cè)趖ry塊中將數(shù)字除以0。這產(chǎn)生一個(gè)ArithmeticException。
發(fā)生異常時(shí),程序?qū)⑻^try塊中的其余代碼。
在這里,我們創(chuàng)建了一個(gè)catch塊來處理ArithmeticException。因此,將catch執(zhí)行塊內(nèi)的語句。
如果該try塊中的所有語句均未生成異常,則跳過catch代碼塊。
對(duì)于每個(gè)try塊,可以有零個(gè)或多個(gè)catch塊。
每個(gè)catch塊的參數(shù)類型指示可以處理的異常類型。多個(gè)catch塊使我們能夠以不同方式處理每個(gè)異常。
class ListOfNumbers { public int[] arrayOfNumbers = new int[10]; public void writeList() { try { arrayOfNumbers[10] = 11; } catch (NumberFormatException e1) { System.out.println("NumberFormatException => " + e1.getMessage()); } catch (IndexOutOfBoundsException e2) { System.out.println("IndexOutOfBoundsException => " + e2.getMessage()); } } } class Main { public static void main(String[] args) { ListOfNumbers list = new ListOfNumbers(); list.writeList(); } }
輸出結(jié)果
IndexOutOfBoundsException => Index 10 out of bounds for length 10
在此示例中,我們聲明了一個(gè)大小為10 的整數(shù)數(shù)組arrayOfNumbers。
我們知道數(shù)組索引總是從0開始。因此,當(dāng)我們嘗試為索引10分配一個(gè)值時(shí),就會(huì)發(fā)生IndexOutOfBoundsException,因?yàn)閿?shù)組arrayOfNumbers的邊界是0到9。
當(dāng)try塊中發(fā)生異常時(shí),
異常被拋出給第一個(gè)catch塊。第一個(gè)catch塊不處理IndexOutOfBoundsException異常,因此它被傳遞給下一個(gè)catch塊。
上面示例中的第二個(gè)catch塊是適當(dāng)?shù)漠惓L幚沓绦?,因?yàn)樗幚鞩ndexOutOfBoundsException。 因此,它被執(zhí)行。
對(duì)于每個(gè)try塊,只能有一個(gè)finally塊。
finally塊是可選的。但是,如果已定義,它將始終執(zhí)行(即使不會(huì)發(fā)生異常)。
如果發(fā)生異常,則在try...catch塊之后執(zhí)行。如果沒有異常發(fā)生,則在try塊之后執(zhí)行。
finally塊的基本語法為:
try { //code } catch (ExceptionType1 e1) { // catch 塊 } catch (ExceptionType1 e2) { // catch 塊 } finally { //finally塊一直執(zhí)行 }
class Main { public static void main(String[] args) { try { int divideByZero = 5 / 0; } catch (ArithmeticException e) { System.out.println("ArithmeticException => " + e.getMessage()); } finally { System.out.println("Finally塊總是執(zhí)行"); } } }
輸出結(jié)果
ArithmeticException => / by zero Finally塊總是執(zhí)行
在此示例中,我們將數(shù)字除以0。這引發(fā)了一個(gè)ArithmeticException被catch塊捕獲,finally塊始終執(zhí)行。
使用finally塊被認(rèn)為是一種很好的做法。這是因?yàn)樗酥匾那謇泶a,例如
可能被return、continue或break語句意外跳過的代碼
關(guān)閉文件或連接
我們已經(jīng)提到,finally總是執(zhí)行,通常是這樣的。但是,在某些情況下,finally塊不執(zhí)行:
使用 System.exit()方法
finally塊中發(fā)生異常
線程被終止
讓我們舉一個(gè)實(shí)例,我們嘗試使用FileWriter創(chuàng)建一個(gè)新文件,并使用PrintWriter寫入數(shù)據(jù)。
import java.io.*; class ListOfNumbers { private int[] list = new int[10]; public ListOfNumbers() { //在列表數(shù)組中存儲(chǔ)整數(shù)值 for (int i = 0; i < 10; i++) { list[i] = i; } } } public void writeList() { PrintWriter out = null; try { System.out.println("進(jìn)入try語句"); //創(chuàng)建一個(gè)新文件OutputFile.txt out = new PrintWriter(new FileWriter("OutputFile.txt")); //將值從列表數(shù)組寫入新創(chuàng)建的文件 for (int i = 0; i < 10; i++) { out.println("Value at: " + i + " = " + list[i]); } } catch (IndexOutOfBoundsException e1) { System.out.println("IndexOutOfBoundsException => " + e1.getMessage()); } catch (IOException e2) { System.out.println("IOException => " + e2.getMessage()); } finally { //檢查PrintWriter是否被打開 if (out != null) { System.out.println("關(guān)閉PrintWriter"); out.close(); } else { System.out.println("PrintWriter無法打開"); } } } } class Main { public static void main(String[] args) { ListOfNumbers list = new ListOfNumbers(); list.writeList(); } }
當(dāng)您運(yùn)行此程序時(shí),可能會(huì)發(fā)生兩種可能性:
try塊中發(fā)生異常
try塊正常執(zhí)行
創(chuàng)建新的FileWriter時(shí)可能會(huì)發(fā)生異常。 如果無法創(chuàng)建或?qū)懭胫付ǖ奈募?,則拋出IOException。
當(dāng)發(fā)生異常時(shí),我們將獲得以下輸出。
進(jìn)入try語句 IOException => OutputFile.txt PrintWriter無法打開
當(dāng)未發(fā)生異常且該try塊正常執(zhí)行時(shí),我們將獲得以下輸出。
進(jìn)入try語句 關(guān)閉PrintWriter
將創(chuàng)建一個(gè)OutputFile.txt,并包含以下內(nèi)容
Value at: 0 = 0 Value at: 1 = 1 Value at: 2 = 2 Value at: 3 = 3 Value at: 4 = 4 Value at: 5 = 5 Value at: 6 = 6 Value at: 7 = 7 Value at: 8 = 8 Value at: 9 = 9
讓我們嘗試在上述示例的幫助下詳細(xì)了解異常處理的流程。
上圖描述了在創(chuàng)建新FileWriter時(shí)發(fā)生異常時(shí)的程序執(zhí)行流程。
為了找到發(fā)生異常的方法,主方法調(diào)用writeList()方法,該方法隨后調(diào)用FileWriter()方法來創(chuàng)建一個(gè)新的OutputFile.txt文件。
發(fā)生異常時(shí),運(yùn)行時(shí)系統(tǒng)將跳過try塊中的其余代碼。
它開始以相反的順序搜索調(diào)用堆棧,以找到合適的異常處理程序。
這里,F(xiàn)ileWriter沒有異常處理程序,因此運(yùn)行時(shí)系統(tǒng)檢查調(diào)用堆棧中的下一個(gè)方法,即writeList。
writeList方法有兩個(gè)異常處理程序:一個(gè)處理IndexOutOfBoundsException,另一個(gè)處理IOException。
然后,系統(tǒng)依次處理這些處理程序。
此示例中的第一個(gè)處理程序處理IndexOutOfBoundsException。 這與try塊引發(fā)的IOException不匹配。
因此,檢查下一個(gè)處理程序是哪個(gè)IOException處理程序。如與引發(fā)的異常類型匹配,因此將執(zhí)行對(duì)應(yīng)catch塊中的代碼。
執(zhí)行異常處理程序后,將執(zhí)行finally塊。
在此場(chǎng)景中,由于FileWriter中發(fā)生了異常,所以PrintWriter對(duì)象out從未打開,因此不需要關(guān)閉。
現(xiàn)在,讓我們假設(shè)在運(yùn)行該程序時(shí)沒有發(fā)生異常,并且try塊正常執(zhí)行。 在這種情況下,將創(chuàng)建并寫入一個(gè)OutputFile.txt。
眾所周知,finally塊的執(zhí)行與異常處理無關(guān)。由于沒有異常發(fā)生,因此PrintWriter打開了并且需要關(guān)閉。這是通過finally塊中的out.close()語句完成的。
從Java SE 7和更高版本開始,我們現(xiàn)在可以用一個(gè)catch塊捕獲不止一種類型的異常。
這樣可以減少代碼重復(fù)并提高代碼的簡(jiǎn)單性和效率。
可以由catch塊處理的每種異常類型都使用豎線(|)分隔。
其語法為:
try { // code } catch (ExceptionType1 | Exceptiontype2 ex) { // catch block }
要了解更多信息,請(qǐng)?jiān)L問Java捕獲多個(gè)異常。
try-with-resources語句是一種try語句,具有一個(gè)或多個(gè)資源聲明。
其語法為:
try (resource declaration) { // use of the resource } catch (ExceptionType e1) { // catch block }
資源是在程序結(jié)束時(shí)要關(guān)閉的對(duì)象。必須在try語句中聲明和初始化它。
讓我們舉個(gè)實(shí)例。
try (PrintWriter out = new PrintWriter(new FileWriter("OutputFile.txt")) { // use of the resource }
try-with-resources語句也稱為自動(dòng)資源管理。該語句在語句末尾自動(dòng)關(guān)閉所有資源。
要了解更多信息,請(qǐng)?jiān)L問Java try-with-resources語句。