Java 異常基礎詳解


1. Java 中的異常

前言:Java 中的異常處理是處理程序運行錯誤時的強大機制之一,它可以保證應用程序的正常流程。

首先我們將了解java異常、異常的類型以及受查和非受查異常之間的區別。

1.1 什么是異常?

字面意義:異常是一種不正常的情況。

在 java 中,異常是擾亂程序正常流程的事件,它是在程序運行時拋出的對象。

1.2 什么是異常處理?

異常處理一種在運行時解決程序錯誤的機制,例如 ClassNotFound、IO、SQL、Remote 等。

1.2.1 異常處理的優勢

異常通常會干擾程序的正常流程,而異常處理的核心優勢是維護程序的正常流程。現在讓我們假設一下:

statement 1;  
statement 2;  
statement 3;  
statement 4;  
statement 5;//發生異常
statement 6;  
statement 7;  
statement 8;  
statement 9;  
statement 10;  

假設你的程序中有10條語句,如果在第5條中出現了一個異常,那么語句6-10將不會繼續執行。如果你使用了異常處理,那么語句6-10的部分將正常執行,這就是我們為什么需要在程序中使用異常處理的原因。

你知道嗎?

  • 受查和非受查異常之間的區別是什么?
  • 代碼int data=50/0;后面發生了什么?
  • 為什么需要使用多個catch塊?
  • finally塊是否有可能不執行?
  • 什么是異常傳遞?
  • throwthrows關鍵字之間的區別?
  • 對方法重寫使用異常處理的4條規則是什么?

現在讓我們帶着以上問題繼續下面的學習。

1.3 Java 異常類的層次結構

throwable

1.4 異常類型

主要有兩種類型的異常:受查和非受查異常,Error被視為非受查異常。Sun公司認為有三種異常類型:

  • 受查異常(Checked Exception)
  • 非受查異常(UnChecked Exception)
  • 錯誤(Error)

1.5 受查和非受查異常之間的區別

1)受查異常

除了RuntimeExceptionError外,繼承自Throwable類的類稱為受查異常,例如:IOException、SQLException 等。受查異常在編譯時進行檢查。

常見的有以下幾個方面:

  • 試圖在文件尾部后面讀取數據
  • 試圖打開一個不存在的文件
  • 試圖根據給定的字符串查找Class對象,而這個字符串表示的類並不存在

2)非受查異常

繼承自RuntimeException類的異常被稱為非受查異常,例如:ArithmeticException、 NullPointerException、 ArrayIndexOutOfBoundsException 等。非受查異常不會在編譯時檢查,而是在運行時進行檢查。

常見的有以下幾個方面:

  • 錯誤的類型轉換
  • 數組訪問越界
  • 訪問null指針

“如果出現了RuntimeException異常,那么一定是你自身的問題”,是一條相當有道理的規則。

3)錯誤(Error)

錯誤是一種無法恢復的異常類型,通常是在java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不應該拋出這種類型的對象。如果出現了這樣的內部錯誤,除了通告給用戶,並盡力的使得程序安全的終止之外,再也無能為力了。這種情況很少出現。

1.6 可能出現異常的常見場景

在某些情況下,可能出現未檢查的異常,它們如下:

1)發生ArithmeticException的場景

如果我們將任何數字除以0,就會出現一個 ArithmeticException 異常。

int a = 50/0;//ArithmeticException  

2)發生NullPointerException的場景

如果變量的值為null,那么調用此變量將會出現 NullPointerException 異常。

String s = null;  
System.out.println(s.length());//NullPointerException  

3)發生NumberFormatException的場景

任何值的格式錯誤,都有肯能發生 NumberFormatException 異常。假設一個字符串變量,其中包含了字符,若將此變量轉換為數字類型,將會發生 NumberFormatException 異常。

String s = "abc";  
int i = Integer.parseInt(s);//NumberFormatException

4)發生ArrayIndexOutOfBoundsException的場景

如果你在一個不存在的的數組索引中插入任何值,則會導致 ArrayIndexOutOfBoundsException 異常。

int a[] = new int[5];  
a[10] = 50; //ArrayIndexOutOfBoundsException  

1.7 Java 異常處理關鍵字

下面是 Java 異常處理中的5個關鍵字:

trycatchfinallythrowthrows

1.8 創建自定義異常類

在程序中,可能會遇到任何標准異常類都沒有能夠充分地描述清楚的問題。在這種情況下,創建自己的異常類就是一件順理成章的事情了。我們需要做的只是定義一個派生於 Exception 的類,或者派生於 Exception 子類的類。例如,定義一個派生於 IOException 的類。

習慣上,定義的類應該包含兩個構造器,一個是默認構造器,一個是描述詳細信息的的構造器(超類 Throwable 的 toString 方法將會打印出這些詳細信息,這在調試中非常有用。)

示例如下:

class FileFormatException extends IOException {
    public FileFormatException() {}
    public FileFormatException(String gripe) {
        super(gripe);
    }
}

現在,就可以拋出自己定義的異常類型了。

String readData(BufferedReader in) throws FileFormatException {
    ...
    while (...) {
        // EOF encountered
        if (ch == -1) {
            if (n < len)
                throw new FileFormatException();
        }
        ...
    }
    return s;
}

2. Java try-catch

將可能發生異常的代碼放在try塊中,且必須在方法中才能使用。try 塊后必須使用catch塊或finally塊。

2.1 Java try 塊

1)try-catch 語法

try{  
// 可能拋出異常的代碼
}catch(Exception_class_Name ref){}  

2)try-finally 語法

try{  
// 可能拋出異常的代碼
}finally{}  

2.2 Java catch 塊

Java catch塊被用於處理異常,必須在try塊后使用。

你可以在一個try塊后使用多個catch

2.3 未使用異常處理的問題

如果我們不使用try-catch處理異常,看看會發生什么。

public class Testtrycatch1 {  
    public static void main(String args[]) {  
        int data=50/0;// 可能拋出異常
        System.out.println("代碼的其余部分...");  
    }  
}  

輸出:

Exception in thread main java.lang.ArithmeticException:/ by zero

如上面的示例所示,代碼的其余部分並沒有執行。("代碼的其余部分..."未打印)

2.4 使用異常處理解決問題

讓我們通過try-catch塊來查看上述問題的解決方案。

public class Testtrycatch2 {  
    public static void main(String args[]) {  

        try {  
            int data = 50/0;  
        }
        catch(ArithmeticException e) {
            System.out.println(e);
        }  

        System.out.println("代碼的其余部分...");  
    }  
}  

輸出:

Exception in thread main java.lang.ArithmeticException:/ by zero
代碼的其余部分...

現在,正如上面的示例所示,代碼的其余部分執行了.(也就是"代碼的其余部分..."被打印)

2.5 Java try-catch 內部工作原理

exceptionobject

Java 虛擬機首先檢查異常是否被處理,如果異常未處理,則執行的一個默認的異常處理程序:

  • 打印異常描述
  • 打印堆棧跟蹤(異常發生方法的層次結構)
  • 終止程序

如果程序員處理了異常,則應用程序按照正常流程執行。


3. 使用多個 catch 塊

如果需要在發生不同異常時執行不同的任務,則需要使用多個 catch 塊。

查看下面一個簡單的多 catch 塊示例。

public class TestMultipleCatchBlock{  
    public static void main(String args[]) {  

        try{  
            int a[] = new int[5];  
            a[5] = 30/0;  
        }  
        catch(ArithmeticException e) {
            System.out.println("任務1已完成");
        }  
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("任務2已完成");
        }  
        catch(Exception e) {
            System.out.println("已完成通用任務");
        }

        System.out.println("代碼的其余部分...");  

    }  
}  

輸出:

任務1已完成
代碼的其余部分...

規則:一次只有一個異常發生,並且一次只執行一個catch塊。

規則: 所有異常必須從最具體到最通用的順序排序,即捕獲ArithmeticException必須在捕獲Exception之前發生。

class TestMultipleCatchBlock1 {  
public static void main(String args[]) {  

        try{  
            int a[]=new int[5];  
            a[5]=30/0;  
        }
        catch(Exception e) {
            System.out.println("已完成通用任務");
        }
        catch(ArithmeticException e) {
            System.out.println("任務1已完成");
        }
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("任務2已完成");
        }  

        System.out.println("代碼的其余部分...");  

    }  
}  

輸出:

Compile-time error

4. Java 嵌套 try 塊

Java try塊中的try塊被稱為try嵌套塊。

4.1 為什么使用 try 嵌套塊?

有時可能會出現一種情況,一個塊的某個部分可能導致一個錯誤,而整個塊的本身可能會導致另一個錯誤。在這種情況下,必須使用嵌套異常處理程序。

語法:

....  
try  
{  
    statement 1;  
    statement 2;  
    try  
    {  
        statement 1;  
        statement 2;  
    }  
    catch(Exception e)  
    {  
        ...
    }  
}  
catch(Exception e) {...}  
....  

4.2 Java try 嵌套塊示例

class Excep6 {
    public static void main(String args[]) {
        try {
            // try 嵌套塊1
            try {
                System.out.println("try 嵌套塊1");
                int b = 39 / 0;
            }
            catch(ArithmeticException e) {
                System.out.println(e);
            }
            // try 嵌套塊2
            try {
                int a[] = new int[5];
                a[5] = 4;
            }
            catch(ArrayIndexOutOfBoundsException e) {
                System.out.println(e);
            }
            System.out.println("try外部塊其他語句...");
        }
        catch(Exception e) {
            System.out.println("handeled");
        }
        System.out.println("正常流...");
    }
}

輸出:

try 嵌套塊1
java.lang.ArithmeticException: / by zero
java.lang.ArrayIndexOutOfBoundsException: 5
try外部塊其他語句...
正常流...

5. Java finally 塊

Java finally 塊是用來執行重要代碼的塊(如關閉連接、流等)。

無論是否處理異常,最終都會執行 finally 塊。

finally 塊緊跟 try 或 catch 塊后:

finally

注意:無論你是否處理異常,在終止程序之前,JVM都將執行finally塊(如果存在的話)

5.1 為什么要使用 finally 塊

finally 塊可以用於放置"clear"代碼,例如關閉文件,關閉連接等。

5.2 使用 finally 塊案例

接下來讓我們來看看在不同情況下使用 finally 塊。

1)案例1

當前沒有發生異常:

class TestFinallyBlock {
    public static void main(String[] args) {
        try {
            int data = 25 / 5;
            System.out.println(data);
        }
        catch (NullPointerException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 塊總是執行");
        }
        System.out.println("代碼的其余部分...");
    }
}

輸出:

5
finally 塊總是執行
代碼的其余部分...

2)案例2

發生異常但未處理:

class TestFinallyBlock1 {
    public static void main(String[] args) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        }
        catch (NullPointerException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 塊總是執行");
        }
        System.out.println("代碼的其余部分...");
    }
}

輸出:

finally 塊總是執行
Exception in thread main java.lang.ArithmeticException:/ by zero

3)案例3

發生異常並處理異常:

public class TestFinallyBlock2 {
    public static void main(String args[]) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        }
        catch(ArithmeticException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 塊總是執行");
        }
        System.out.println("代碼的其余部分...");
    }
}

輸出:

Exception in thread main java.lang.ArithmeticException:/ by zero
finally 塊總是執行
代碼的其余部分...

規則:對於 try 塊可以有0個或多個 catch 塊,但僅僅只能有一個 finally 塊。

規則:如果程序退出(通過調用 System.exit() 或通過導致進程中止的致命錯誤),finally塊將不會被執行。


6. Java 拋出異常

6.1 Java throw 關鍵字

Java throw 關鍵字用於顯示的拋出異常。

我們可以使用 throw 關鍵字在 Java 中拋出檢查(Checked)或未檢查(UnChecked)異常。throw 關鍵字主要用於拋出自定義異常。

Java throw 語法如下:

throw exception;  

拋出IOException異常的例子:

throw new IOException("sorry device error");  

6.2 Java throw 示例

在本例中,我們創建了一個將整數值作為參數的 validate 方法。如果年齡小於18歲,我們將拋出一個ArithmeticException異常,否則打印一條消息"歡迎投票"。

public class TestThrow1 {
    static void validate(int age) {
        if(age < 18)  
            throw new ArithmeticException("無效");
        else  
            System.out.println("歡迎投票");
    }

    public static void main(String args[]) {
        validate(13);
        System.out.println("代碼的其余部分...");
    }
}

輸出:

Exception in thread main java.lang.ArithmeticException:無效

7. Java 異常傳遞

異常首先從堆棧頂部拋出,如果未捕獲,則將調用堆棧下降到前一個方法,如果沒有捕獲,則將異常再次下降到先前的方法,以此類推,知道它們被捕獲或到達調用堆棧底部為止。以上稱為異常傳遞。

規則:默認情況下,非受查異常在調用鏈中(傳遞)轉發。

異常傳遞示例:

class TestExceptionPropagation1 {
    void m(){
        int data = 50 / 0;
    }
    void n() {
        m();
    }
    void p() {
        try{
            n();
        }
        catch(Exception e) {
            System.out.println("異常處理器");
        }
    }

    public static void main(String args[]) {
        TestExceptionPropagation1 obj = new TestExceptionPropagation1();
        obj.p();
        System.out.println("正常流...");
    }
}

輸出:

異常處理器
正常流...

propagation

在上面的示例中。異常發生在 m() 方法中,如果未對其進行處理,則將其傳遞到未處理它的前 n() 方法,再次將其傳遞到處理異常的 p() 方法。

可以在 main()、p()、n()、p()、 m() 中的任何方法中處理異常。

規則:默認情況下,受查異常不會在調用鏈中(傳遞)轉發。

用於描述受查異常不會在程序中傳遞的示例:

class TestExceptionPropagation2{
    void m(){
        throw new java.io.IOException("設備異常"); // 受查異常
    }
    void n(){
        m();
    }
    void p(){
        try{
            n();
        }
        catch(Exception e){
            System.out.println("異常處理器");
        }
    }
    public static void main(String args[]){
        TestExceptionPropagation2 obj=new TestExceptionPropagation2();
        obj.p();
        System.out.println("正常流...");
    }
}

輸出:

Compile Time Error

編譯時發生一個錯誤,證明受查異常並不會在程序中進行傳遞。


8. Java throws 關鍵字

Java throws 關鍵字被用於聲明一個異常。它給程序員提供了一個信息,說明可能會發生異常,所以程序員最好提供異常處理代碼,以保證程序正常的流程。

異常處理主要用於處理受查異常,如果出現任何非受查異常,如"NullPointerException",都是程序員自身的錯誤,請認真檢查你的代碼。

8.1 Java throws 語法

return_type method_name() throws exception_class_name {  
    // method code  
}  

8.2 應該聲明哪個異常?

僅僅聲明受查異常,因為:

  • 非受查異常:程序員應該更正代碼以確保代碼正確無誤。
  • Error:無法控制,如果出現了 VirtualMachineErrorStackOverflowError等異常,將無法進行任何操作。

8.3 Java throws 優勢

使用 throws 聲明受查異常后,使得受查異常可以在調用堆棧中進行(傳遞)轉發。它向處理該異常的方法提供異常信息。

8.4 Java throws 示例

下面的示例描述了受查異常可以通過throws關鍵字進行傳遞:

import java.io.IOException;
class Testthrows1{
    void m() throws IOException{
        throw new IOException("設備異常"); // 受查異常
    }
    void n()throws IOException{
        m();
    }
    void p(){
        try{
            n();
        }
        catch(Exception e){
            System.out.println("異常處理器");
        }
    }
    public static void main(String args[]){
        Testthrows1 obj=new Testthrows1();
        obj.p();
        System.out.println("正常流...");
    }
}

輸出:

異常處理器
正常流...

規則:如果你正在調用一個聲明了異常的方法,則必須捕獲或聲明異常。

現在有兩種情況:

  • 情況1:你遇到了一個異常,使用 try-catch 處理了異常。
  • 情況2:你聲明了異常,使用方法指定拋出。

1) 情況1:處理了異常

  • 在這種情況下,如果你處理了異常,則不管程序是否出現了異常,程序都將繼續執行。
import java.io.*;
class M{
    void method() throws IOException{
        throw new IOException("設備異常");
    }
}
public class Testthrows2{
    public static void main(String args[]){
        try{
            M m = new M();
            m.method();
        }
        catch(Exception e){
            System.out.println("異常處理器");
        }
        System.out.println("正常流...");
    }
}

輸出:

異常處理器
正常流...

2) 情況2:聲明了異常

  • A)如果聲明了異常,但代碼未出現異常,程序將正常執行。
  • B)如果聲明了異常且發生了異常,則在運行時拋出異常,因為程序會拋出不處理的異常。

A)聲明了異常但未發生異常:

import java.io.*;
class M{
    void method()throws IOException{
        System.out.println("執行設備操作");
    }
}
class Testthrows3{
    public static void main(String args[])throws IOException{
        // 聲明了異常
        M m=new M();
        m.method();
        System.out.println("正常流...");
    }
}

輸出:

執行設備操作
正常流...

B)聲明了異常且發生了異常:

import java.io.*;
class M{
    void method()throws IOException{
        throw new IOException("設備錯誤");
    }
}
class Testthrows4{
    public static void main(String args[])throws IOException{
        // 聲明了異常  
        M m=new M();
        m.method();
        System.out.println("正常流...");
    }
}

輸出:

Runtime Exception

程序編譯時將直接出現了一個編譯錯誤。

8.5 throw 與 throws 區別

No. throw throws
1) Java throw 關鍵字用於顯示的拋出異常 Java throws 關鍵字用於聲明一個異常
2) 受查異常不能只使用 throw 進行傳遞 受查異常可以通過 throws 進行傳遞
3) Throw 后面跟着一個異常實例 Throws 后面跟着一個異常類
4) 在方法中使用 Throw Throws 與方法簽名一起使用
5) 你不能拋出多個異常 你可以聲明多個異常,例如public void method() throws IOException,SQLException

1)Java throw 示例:

void m(){  
    throw new ArithmeticException("sorry");  
}  

2)Java throws 示例:

void m()throws ArithmeticException{  
    // method code  
}  

3)Java throw 和 throws 示例:

void m()throws ArithmeticException{  
    throw new ArithmeticException("sorry");  
}  

8.6 思考:可以重新拋出一個異常嗎?

答案當然是可以的,可以在 catch 塊中拋出相同的異常。這種方法通常用於只想記錄一個異常,但不做任何改變。

代碼示例:

try {
    // access the database
}
catch (Exceptiom e) {
    logger.log(level, message, e);
    throw e;
}

在 Java SE7 之前,這種方法存在一個問題,假設這段代碼在以下方法中:

public void updateRecord() throws SQLException

Java 編譯器查看 catch 塊中的 throw 語句,然后查看 e 的類型,會指出這個方法可以拋出任何 Exception 而不僅僅是 SQLException。現在這個問題已經有所改進,編譯器會追蹤到 e 來自 try 塊。假設這個 try 塊僅有的受查異常是 SQLException 實例,另外,假設 e 在 catch 塊中未改變,將外圍方法聲明為 throws SQLException 是合法的。


9. Final 和 Finally 和 Finalize 對比

Final 和 Finally 和 Finalize 三者之間的差異如下:

No. final finally finalize
1) final 用於對類、方法和變量加以限制,final 類不能被繼承,final 方法不能被重寫,final 變量不能被更改 finally 用於放置重要的代碼,無論異常是否被處理它都會執行 finalize 用於在對象被垃圾回收之前執行清理操作
2) final 是一個關鍵字 finally 是一個塊 finalize 是一個方法

1)Java final 示例:

class FinalExample{
    public static void main(String[] args){
        final int x = 100;
        x = 200; // final 修飾的變量不能被更改
        // 編譯時將出錯
    }
}

2)Java finally 示例:

class FinallyExample{
    public static void main(String[] args){
        try{
            int x = 300;
        }
        catch(Exception e){
            System.out.println(e);
        }
        finally{
            System.out.println("finally 塊始終被執行");
        }
    }
}

3)Java finalize 示例:

class FinalizeExample{
    public void finalize(){
        System.out.println("finalize called");
    }
    public static void main(String[] args){
        FinalizeExample f1 = new FinalizeExample();
        FinalizeExample f2 = new FinalizeExample();
        f1 = null;
        f2 = null;
        System.gc();
    }
}

10. 異常處理方法的重寫

關於重寫異常處理方法的規則如下:

  • 超類方法沒有聲明異常:
    如果超類方法沒有聲明異常,則子類重寫方法不能聲明受查異常,但可以聲明非受查異常。
  • 超類方法聲明了異常:
    如果超類方法聲明了異常,則子類重寫方法可以聲明與超類方法相同的異常,也可以不聲明異常。若父類方法聲明父類異常,子類重寫方法聲明子類異常也可以,反之不可以。

1)如果超類方法沒有聲明異常

超類方法未聲明異常,子類重寫方法聲明受查異常的示例:

import java.io.*;
class Parent{
    void msg(){
        System.out.println("parent");
    }
}
class TestExceptionChild extends Parent{
    void msg() throws IOException{
        System.out.println("Child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild();
        p.msg();
    }
}

輸出:

Compile Time Error

超類方法未聲明異常,子類重寫方法聲明非受查異常的示例:

import java.io.*;
class Parent{
    void msg(){
        System.out.println("parent");
    }
}
class TestExceptionChild1 extends Parent{
    void msg() throws ArithmeticException{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild1();
        p.msg();
    }
}

輸出:

child

2)如果超類方法聲明了異常

A)超類方法聲明了異常,子類重寫方法聲明不相同父類異常的示例:

import java.io.*;
class Parent{
    // 聲明了子類異常
    void msg() throws ArithmeticException{
        System.out.println("parent");
    }
}
class TestExceptionChild2 extends Parent{
    // 聲明了父類異常
    void msg() throws Exception{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild2();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

Compile Time Error

B)超類方法聲明了異常,子類重寫方法聲明相同異常的示例:

import java.io.*;
class Parent{
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild3 extends Parent{
    void msg()throws Exception{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild3();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

child

C)超類方法聲明了異常,子類重寫方法聲明不相同子類異常的示例:

import java.io.*;
class Parent{
    // 聲明了父類異常
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild4 extends Parent{
    // 聲明了子類異常
    void msg()throws ArithmeticException{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild4();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

child

D)超類方法聲明了異常,子類重寫方法未聲明異常的示例:

import java.io.*;
class Parent{
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild5 extends Parent{
    void msg(){
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild5();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

child

參考文章:https://www.javatpoint.com/exception-handling-in-java
參考書籍:《Java核心技術 卷1》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM