Java中供了一種識別和響應錯誤情況的一致性機制——異常處理機制,有效地異常處理能夠使程序具有更強的健壯性、易於調試。本篇博客將詳細介紹Java中的異常處理(Exception)。
目錄:
在使用計算機語言進行項目開發的過程中,即使程序員把代碼寫得盡善盡美,在系統的運行過程中仍然會遇到一些問題,因為存在很多問題不是靠代碼能夠避免的,比如:客戶輸入數據的格式,讀取文件是否存在,網絡是否始終保持通暢等。這時就需要借助Java異常處理機制來解決這部分問題,合理的使用異常處理機制可以降低bug的產生,提高程序的健壯性。
☍ 異常概述與異常體系結構
▴ 異常概述
☃ 異常:在Java語言中,將程序執行中發生的不正常情況稱為“異常”。(開發過程中的語法錯誤和邏輯錯誤不是異常)
☃ Java程序在執行過程中所發生的異常事件可分為兩類:
➢ Error:Java虛擬機無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。比如:棧溢出錯誤:StackOverflowError和堆溢出錯誤:OOM(OutOfMemoryError)。一般不編寫針對性的代碼進行異常處理,修改導致錯誤出現的代碼即可。
public class ExceptionTest {
public static void main(String[] args) {
//棧(statck)溢出異常: java.lang.StackOverflowError
//main(args);
//堆(heap)溢出異常:java.lang.OutOfMemoryError
//Integer[] arr = new Integer[1020*1024*1024];
}
}
➢ Exception:其它因編程錯誤或偶然的外在因素導致的一般性問題,可以使用針對性的代碼進行處理。例如:
✔ 空指針訪問
✔ 試圖讀取不存在的文件
✔ 網絡連接中斷
✔ 數組下標越界
☃ 對於這些錯誤,一般有兩種 解決方法:一是遇到錯誤就終止程序的運行(默認)。另一種方法是由程序員在編寫程序時,就考慮到錯誤的
檢測、錯誤消息的提示,以及錯誤的處理(主要解決異常的方法)。
☃ 捕獲錯誤最理想的是在編譯期間,但有的錯誤只有在運行時才會發生,如數組越界。
☃ 按異捕獲的時間可分為:編譯時異常和運行時異常。

▴ Exception異常分類:編譯時異常&運行時異常
編譯時異常
☃ 是指編譯器要求必須處置的異常。即程序在運行時由於外界因素造成的一般性異常。編譯器要求Java程序必須捕獲或聲明所有的編譯時異常。
☃ 在Java中, Exception類中除了RuntimeException類及其子類外都是編譯時異常,對應上圖中受檢異常。編譯時異常的特點是Java編譯器會對其進行檢查,如果存在異常情況就必須對異常進行處理,否則程序無法通過編譯,如讀取空文件的異常。
☃ 對於這類異常,如果程序不處理,可能會帶來意想不到的結果。
☃ 處理編譯時期的異常有兩種方式,具體如下:
(1)使用try…catch語句對異常進行捕獲處理。
(2)使用throws關鍵字聲明拋出異常,等待調用者對其處理,如果調用者不處理繼續拋出異常。
運行時異常
☃ 是指編譯器不要求強制處置的異常。一般是指編程時的邏輯錯誤,是程序員應該積極避免出現的異常。java.lang.RuntimeException類及它的子類都是運行時異常。
☃ 運行時異常的特點是Java編譯器不會對其進行檢查,也就是說,當程序中出現這類異常時,即使沒有使用try…catch語句捕獲或使用throws關鍵字聲明拋出,程序也能編譯通過(運行時可能會報錯),如數組角標越界異常。
☃ 對於這類異常,可以視情況選擇處理(不確定邏輯是否正確)或者不處理(確定邏輯正確時),因為這類異常很普遍,若全處理可能會對程序的可讀性和運行效率產生影響。
☍ 常見異常
▴ 常見運行時異常:java.lang.RuntimeException及其子類
空指針異常NullPointerException
public void NullPointerExceptionDemo() {
Object obj = null;
System.out.println(obj.toString());
}
數組角標越界異常:ArrayIndexOutOfBoundsException
public void ArrayIndexOutOfBoundException1() {
int[] arr = new int[3];
System.out.println(arr[5]);
}
public void ArrayIndexOutOfBoundException2() {
String string = "abcd";
System.out.println(string.toCharArray()[5]);
}

字符串角標越界異常:StringIndexOutOfBoundsException
public void StringIndexOutOfBoundExceptionDemo() {
String str = "abcd";
System.out.println(str.charAt(5));
}

類型轉換異常:java.lang.ClassCastException
public void ClassCastExceptionDemo() {
class Dog{
}
class Cat{
}
//Dog dog = new Cat(); 編譯不通過,語法錯誤
Object obj = new Dog();
Cat cat = (Cat)obj;
}

數字格式異常:java.lang.NumberFormatException
public void NumberFormatExceptionDemo(){
String str = "abc";
//Integer number = new Integer(str);
int num = Integer.parseInt(str);
}

控制台輸入格式異常:java.util.InputMismatchExceptionDemo
public void InputMismatchExceptionDemo() {
Scanner scanner = new Scanner(System.in);
System.out.print("請輸入數字:");
int num = scanner.nextInt();
//控制台輸入不符合規則時報的異常:如此時輸入非數字字符
System.out.println(num);
}

算術運算異常:java.lang.ArithmeticException
public void ArithmeticExceptionDemo() {
int a = 4;
int b = 0;
System.out.println(a / b);
}

▴ 常見編譯時異常:除RunTimeException外的其他異常類
☃ 編譯時異常會在編譯時就提醒,要求程序員在編譯時就對該異常進行處理

☃ 常見的編譯時異常有:
◌ SQL異常:java.sql.SQLException(提供關於數據庫訪問錯誤或其他錯誤信息的異常)
◌ IO流異常: java.io.IOExeption(當發生某種 I/O 異常時,拋出此異常。此類是失敗或中斷的 I/O 操作生成的異常的通用類。)
• java.io.FileNotFoundException(當試圖打開指定路徑名表示的文件失敗時,拋出此異常)
• java.io.EOFException(表示在輸入過程中意外地到達文件結束或流結束。這個異常主要由數據輸入流用來表示流的結尾。請注意,許多其他輸入操作在流的末尾返回特殊值,而不是拋出異常。)
◌ 無法找到指定的類異常: java.lang.ClassNotFoundException
◌ 數據格式異常:java.util.zip.DataFormatException(當數據格式發生錯誤時,拋出此異常)
◌ 找不到方法的異常:NoSuchMethodException(無法找到某一特定方法時,拋出該異常)
◌ 中斷異常: java.lang.InterruptedException(線程在等待、睡眠或以其他方式占用時拋出,線程在活動間中斷。偶爾,一個方法可能希望測試當前線程是否已被中斷,如果是,立即拋出該異常)
☍ 異常處理機制
在編寫程序時,經常要在可能出現錯誤的地方加上檢測的代碼,
如進行x/y運算時,要檢測分母為0,數據為空,輸入的不是數據而是字符等。過多的if-else分支會導致程序的代碼加長、臃腫、可讀性差。因此采用異常處理機制。
☃ Java采用的異常處理機制,是將異常處理的程序代碼集中在一起,與正常的程序代碼分開,使得程序簡潔、優雅,並易於維護。
☃ Java異常處理的方式分為:try-catch-finally模式和throws+異常類型模式。
☃ Java提供的是異常處理的抓拋模型。
☃Java程序的執行過程中如出現異常,會生成一個異常類對象,該異常對象將被提交給Java運行時系統,這個過程稱為拋出
(throw)異常。
☃ 當程序一旦拋出異常對象后,{}中其后的代碼就不再執行。
異常對象的生成:
☃ 由虛擬機自動生成:程序運行過程中,虛擬機檢測到程序發生了問題,如果在當前代碼中沒有找到相應的處理程序,就會在后台自動創建一個對應異常類的實例對象並拋出——自動拋出
☃ 由開發人員手動創建,如:Exception exception = new ClassCastException();創建好的異常對象不拋出對程序沒有任何影響,和創建一個普通對象一樣
☃ 如果一個方法內拋出異常,該異常對象會被拋給調用者方法中處理。如果異常沒有在調用者方法中處理,它繼續被拋給這個調用方法的上層方法。這個過程將一直繼續下去,直到異常被處理。這一過程稱為捕獲(catch)異常。
☃ 如果一個異常回到main()方法,並且main()也不處理,則程序運行終止。
☃ 程序員通常只能處理Exception,而對Error無能為力。
✦ 為保證程序正常執行,代碼必須對可能出現的異常進行處理,代碼執行過程中可以將異常拋給調用者,但異常必須在某一時刻被處理,而不是一直向上拋出異常而不進行處理。
☍ 異常處理機制一:try-catch-finally
try-catch:
☃ 異常處理是通過try-catch-finally語句實現的。

try {
//可能產生異常的代碼
}
catch (異常類型1 變量名1) {
// 當產生指定類型(包括子類)的異常時的處理措施
}
catch (異常類型2 變量名2) {
// 當產生指定類型(包括子類)的異常時的處理措施
}
...
finally { //finally根據情況可加可不加
//無論是否發生異常,都會執行的語句
}
//其他代碼
☃ 捕獲異常的第一步是用try{…}語句塊選定捕獲異常的范圍,將可能出現異常的代碼放在try語句塊中。一旦出現異常,就會生成一個對應的異常類的對象,根據此對象的類型,去catch中進行匹配。
☃ 在catch(Exceptiontype e)語句塊中是對異常對象進行處理的代碼。每個try語句塊可以伴隨一個或多個catch語句,用於處理可能產生的不同類型的異常對象。
☃ 一旦try中的異常匹配到某一個catch時,就進入catch中進行異常的處理,一旦處理完成跳出當前的try-catch結構,繼續向下執行其后的代碼。
↪ 如果明確知道產生的是何種異常,可以用該異常類作為catch的參數;也可以用其父類作為catch的參數。
比如 :可以用 ArithmeticException類作為參數的地方,就可以用RuntimeException類作為參數,或者用所有異常的父類Exception類作為參數。但不能是與ArithmeticException類無關的異常,如NullPointerException(catch中的語句將不會行)。
☃ catch中的異常類型如果沒有子父類關系,則聲明順序沒有影響,如果catch中異常類型如果滿足子父類關系,則要求子類一定聲明在父類的上面,否則報錯。
捕獲異常的有關信息:
☃ 與其它對象一樣,可以訪問一個異常對象的成員變量或調用它的
方法。
➣ getMessage()獲取異常信息,返回字符串
➣ printStackTrace() 獲取異常類名和異常信息,以及異常出現在程序中的位置。返回值void。
public static void tryCatchFinally() {
try {
//可能產生異常的代碼
int arr[] = new int[3];
System.out.println("開始執行");
arr[4] = 232;
//出現異常后,{}其后的代碼不再執行
System.out.println(arr[4]);
}catch (NumberFormatException e) {
//數字格式異常
System.out.println("數字格式異常");
}
/*子類異常一定聲明在父類的上面
catch (IndexOutOfBoundsException e) {
}*/
catch (ArrayIndexOutOfBoundsException e) {
// 當產生指定類型(包括子類)的異常時的處理措施
System.out.println("數組越界異常");
//異常對象中的getMessage()方法,返回字符串類型的異常信息
System.out.println("getMessage方法:" + e.getMessage());
//異常對象中的printStackTrace()方法
e.printStackTrace(); //控制台輸出詳細的異常信息
} catch (Exception e) {
System.out.println("出現異常");
}finally {
//無論是否發生異常,都會執行的語句
System.out.println("ArrayIndexOutOfBoundsException Test");
}
//其他代碼
//try-catch中定義的變量出了try-catch范圍就不能使用了,除非將變量定義在try-catch前面
//arr[1] = 10;
System.out.println("orther code");
}
輸出結果:
✦ 使用try-catch-finally處理編譯時異常,使得程序在編譯時就不再報錯,但是在運行時仍可能報錯使用try-catch-finally相當於將一個編譯時可能出現的異常,延遲到運行時出現。
finally:
☃ finally語句和catch語句是任選的,但必須至少存在一個。
☃ 捕獲異常的最后一步是通過finally語句為異常處理提供一個統一的出口,使得在控制流轉到程序的其它部分以前,能夠
對程序的狀態作統一的管理。
☃ 不論在try代碼塊中是否發生了異常事件,catch語句是否執行,catch語句中是否有異常,catch語句中是否有return,finally塊中的語句都會被執行。
☃ 由於finally一定會執行,所以若方法有返回值,finally中如果也定義了返回值,則會覆蓋try-catch中的返回值
@SuppressWarnings("finally")
public int test1() {
try {
int a = 23;
int b = 0;
System.out.println(a / b);
return 0;
} catch (ArithmeticException e) {
e.printStackTrace();
return -1;
} catch (Exception e) {
e.printStackTrace();
return -1;
} finally {
System.out.println("執行到最后了");
//返回值會覆蓋try-catch中的返回值
return 1;
}
}
輸出結果:
☃ 由於JVM無法自動回收類似數據庫鏈接、輸入輸出流、網絡編程Socket等的資源,我們需要手動關閉釋放資源,這時定義在finally中最為合適(防止因為異常影響導致代碼未執行)
public void test2() {
//定義在try-catch前面,方便try-catch后面的使用
FileInputStream fis = null;
try {
File file = new File("abc.txt");
fis = new FileInputStream(file);
int data = fis.read();
while(data != -1) {
System.out.print((char)data);
data = fis.read();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally {
//關閉流,try-catch可以嵌套使用
try {
if(fis != null) //避免空指針
fis.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
System.out.println("end");
}
捕獲異常vs不捕獲異常
☃ RuntimeException類或是它的子類,這些類的異常的特
點是:即使沒有使用try和catch捕獲,Java自己也能捕獲,並且編譯通過(但運行時會發生異常使得程序運行終止)。
☃ 如果拋出的異常是IOException等類型的非運行時異常,則必須捕獲,否則編譯錯誤。也就是說,我們必須處理編譯時異常,將異常進行捕捉,轉化為運行時異常。
☍ 異常處理機制二:throws拋出異常
聲明拋出異常
☃ 聲明拋出異常是Java中處理異常的第二種方式
☄ 如果一個方法(中的語句執行時)可能生成某種異常,但是並不能確定如何處理這
種異常,則此方法應顯示地聲明拋出異常,表明該方法將不對這些異常進行處理,而由該方法的調用者負責處理。
☄ 在方法聲明中用throws語句可以聲明拋出異常的列表,throws后面的異常類型可以是方法中產生的異常類型,也可以是它的父類 。
☃ throws拋出異常得方式寫在方法聲明初處,指明此方法執行時,可能會拋出的異常類型一旦方法體運行時出現異常,仍會在代碼處生成一個異常對象並且方法體中的代碼不會繼續向下執行,此對象滿足throws異常類型時,就會拋出,方法體內,異常后面的代碼不再執行
☃ 如果一個方法內拋出異常,該異常對象會被拋給調用者方法中處理。如果異常沒有在調用者方法中處理,它繼續被拋給這個調用方法的上層方法。這個過程將一直繼續下去,直到異常被處理或最終拋給JVM虛擬機。如果一個異常回到main()方法,並且main()也不處理,則程序運行終止。
↪ try-catch-finally:正真的處理了異常;throws 異常:只是將異常拋給了調用者,並沒有真正處理異常
public class ThrowsException {
static FileInputStream fis = null;
public static void main(String[] args) {
ThrowsException t = new ThrowsException();
//調用者處理異常
try {
t.getReadFile();;
} catch (IOException e) {
System.out.println("出現異常");
e.printStackTrace();
}finally {
//關閉io流
try {
if(fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("finally");
}
}
//調用者繼續拋出異常,IOException是FileFoundException父類,若對於兩個異常不做區分處理,則拋出父類異常即可
public void getReadFile() throws IOException {
String filePath = "abc.txt";
readFile(filePath);
}
//聲明拋出異常
public void readFile(String filePath) throws FileNotFoundException,IOException{
File file = new File(filePath);
// 讀文件的操作可能產生FileNotFoundException 類型的異常
fis = new FileInputStream(file);
int data = fis.read();
while(data != -1) {
System.out.print((char)data);
data = fis.read();
}
System.out.println("方法體末尾");
}
}
輸出結果:
拋出異常過程
重寫方法聲明拋出異常的原則
☃ 重寫方法不能拋出比被重寫方法范圍更大的異常類型。在多態的情況下,對methodA()方法的調用-異常的捕獲按父類聲明的異常處理。
☃ 如果父類中被重寫的方法沒有throws方式處理異常,則子類重寫的方法也不能使用throws拋出異常,意味着此時如果子類重寫的方法中有異常,必須使用try-catch-finally方式處理。
public class A {
public void methodA() throws IOException {……}
public void methodB() {……}
}
public class B1 extends A {
public void methodA() throws FileNotFoundException {……}
//public void methodB() throws FileNotFoundException {……} 報錯
}
public class B2 extends A {
public void methodA() throws Exception { // 報錯……}
public void methodB(){
try{...}
catch(ExcptionType e){...}
finally{...}
}
}
異常處理方式的選擇
執行的方法A中,先后又調用了另外幾個方法,這幾個方法又遞進的關系(如:方法A調用方法B,B需要得到方法C中的數據,方法C需要得到方法D中的數據),此時建議這幾個被調用的方法使用用throws拋出異常,而執行的方法A考慮使用try-catch-finally方式處理,這樣可以保證在執行到A方法時,其他被調用的方法如果發生異常不會被處理掉而影響A的執行,同時便於異常的集中處理。
try-catch-finally異常處理方式和throws異常處理方式只能二選一使用,不能同時使用,因為同時使用,由於try-catch-finally已經將異常處理了,再拋出異常就沒意義了。
☍ 手動拋出異常
Java異常類對象除在程序執行過程中出現異常時由系統自動生成並拋出(以上都是JVM自動生成),也可根據需要使手動創建並拋出異常。
☃ 首先要生成異常類對象,然后通過throw語句實現拋出操作(提交給Java運
行環境)。
public class ThrowTest {
public static void main(String[] args) {
ThrowTest t = new ThrowTest();
try {
t.method(-2);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public void method(int num) throws Exception{
int i = 0;
if(num >= 0) {
i = num;
}else {
throw new Exception("數字不能小於0");
}
}
}
☍ 用戶自定義異常類
☃ 一般地,用戶自定義異常類都是RuntimeException的子類
☃ 自定義異常類通常需要編寫幾個重載的構造器
☃ 自定義異常需要提供serialVersionUID序列號
☃ 自定義的異常通過throw拋出
☃ 自定義異命名必須要規范,能夠見名知意,當異常出現時,可以根據名字判斷異常類型
☃ 用戶自己的異常類必須繼承現有的異常類,通常是Exception & RuntimeException
public class MyExceptionTest {
public void regist(int num) throws MyException {
if (num < 0)
throw new MyException("人數為負值,不合理", 3);
else
System.out.println("登記人數" + num);
}
public void manager() {
try {
regist(-1);
} catch (MyException e) {
System.out.println("登記失敗," + e.getMessage());
System.out.print("出錯種類" + e.getId());
}
System.out.print("本次登記操作結束");
}
public static void main(String args[]) {
MyExceptionTest t = new MyExceptionTest();
t.manager();
}
}
class MyException extends RuntimeException {
// 自定義異常序列標識號,用於區分異常
static final long serialVersionUID = 14232463463463535L;
private int idnumber;
public MyException() {}
public MyException(String message) {
super(message);
}
public MyException(String message, int id) {
super(message);
this.idnumber = id;
}
public int getId() {
return idnumber;
}
}
輸出結果:
☍ 總結
本博客與CSDN博客༺ཌ༈君☠纖༈ད༻同步發布
