你好!在今天的課程中,我們將討論 Java 異常。日常生活充滿了我們意想不到的情況。例如,您早上起床尋找手機充電器,但到處都找不到。你去洗手間洗澡才發現水管都結冰了。你上了車,但它無法啟動。人類能夠很容易地應對這種不可預見的情況。在本文中,我們將嘗試弄清楚 Java 程序如何處理它們。

什麼是 Java 異常?

在編程世界中,程序執行中的錯誤和不可預見的情況被稱為異常。在程序中,異常可能由於無效的用戶操作、磁盤空間不足或與服務器的網絡連接丟失而發生。編程錯誤或 API 的不正確使用也可能導致異常。與現實世界中的人類不同,程序必須確切地知道如何處理這些情況。為此,Java 有一種稱為異常處理的機制。

關於關鍵字的幾句話

Java中的異常處理是基於在程序中使用以下關鍵字:
  • try - 定義一個可能發生異常的代碼塊;
  • catch - 定義處理異常的代碼塊;
  • finally - 定義一個可選的代碼塊,如果存在,則無論 try 塊的結果如何都會執行。
這些關鍵字用於在代碼中創建特殊結構:try{}catchtry{}catch{}finallytry{}finally{}
  • throw - 用於引發異常;
  • throws - 在方法簽名中用於警告該方法可能會拋出異常。
在 Java 程序中使用關鍵字的例子:

// This method reads a string from the keyboard

public String input() throws MyException { // Use throws to warn 
// that the method may throw a MyException
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String s = null;
// We use a try block to wrap code that might create an exception. In this case,
// the compiler tells us that the readLine() method in the 
// BufferedReader class might throw an I/O exception
    try {
        s = reader.readLine();
// We use a catch block to wrap the code that handles an IOException  
    } catch (IOException e) {
        System.out.println(e.getMessage());
// We close the read stream in the finally block
    } finally {
// An exception might occur when we close the stream if, for example, the stream was not open, so we wrap the code in a try block
        try {
            reader.close();
// Handle exceptions when closing the read stream
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    if (s.equals("")) {
// We've decided that an empty string will prevent our program from working properly. For example, we use the result of this method to call the substring(1, 2) method. Accordingly, we have to interrupt the program by using throw to generate our own MyException exception type.
        throw new MyException("The string cannot be empty!");
    }
    return s;
}

為什麼我們需要例外?

讓我們看一個來自現實世界的例子。想像一下,一段高速公路有一座承載能力有限的小橋。如果一輛比橋的極限重的汽車駛過它,它可能會倒塌。溫和地說,駕駛員的情況將變得異常。為避免這種情況,交通部門會在出現問題之前在道路上安裝警告標誌。看到警告標誌,司機將他或她的車輛重量與橋樑的最大重量進行比較。如果車輛太重,司機會繞道行駛。交通部門一是允許卡車司機在必要時改變路線,二是警告司機注意主幹道上的危險,三是警告司機在某些情況下不得使用這座橋。 Java 中的異常 - 2能夠防止和解決程序中的異常情況,使其繼續運行,這是在 Java 中使用異常的原因之一。異常機制還允許您通過驗證(檢查)任何輸入來保護您的代碼 (API) 免遭不當使用。現在假設您是交通部門。首先,您需要知道駕車者可能會遇到麻煩的地方。其次,您需要創建和安裝警告標誌。最後,如果主要路線出現問題,您需要繞行。在 Java 中,異常機制以類似的方式工作。在開發過程中,我們使用try塊圍繞代碼的危險部分構建“異常屏障”,我們使用 catch {}提供“備用路由”塊,我們在finally{}塊中編寫無論如何都應該運行的代碼。如果我們不能提供“備用路線”或者我們想給用戶選擇的權利,我們至少要警告他或她危險。為什麼?試想一下,一個司機在沒有看到任何警告標誌的情況下,到達一座他無法通過的小橋時的憤慨!在編程中,當編寫我們的類和方法時,我們不能總是預見到它們可能會被其他開發人員如何使用。因此,我們無法預見 100% 正確的解決異常情況的方法。也就是說,警告其他人可能出現異常情況是一種很好的形式。Java 的異常機制讓我們可以通過throws來做到這一點關鍵字——本質上是一個聲明,表明我們的方法的一般行為包括拋出異常。因此,任何使用該方法的人都知道他或她應該編寫代碼來處理異常。

警告別人“麻煩”

如果您不打算在您的方法中處理異常,但想警告其他人可能會發生異常,請使用throws關鍵字。方法簽名中的 this 關鍵字意味著,在某些情況下,該方法可能會拋出異常。此警告是方法接口的一部分,並允許其用戶實現自己的異常處理邏輯。在 throws 之後,我們指定拋出的異常類型。這些通常派生自 Java 的Exception類。由於 Java 是一種面向對象的語言,因此所有異常都是 Java 中的對象。 Java 中的異常 - 3

異常層次結構

當程序運行時發生錯誤時,JVM 從 Java 異常層次結構中創建一個適當類型的對象——一組可能的異常,它們來自一個共同的祖先——Throwable。我們可以將異常的運行時情況分為兩組:
  1. 程序無法恢復並繼續正常運行的情況。
  2. 可以恢復的情況。
第一組包括涉及從Error類派生的異常的情況。這些是由於 JVM 故障、內存溢出或系統故障而發生的錯誤。它們通常表示無法通過軟件修復的嚴重問題。在 Java 中,編譯器不檢查此類異常的可能性,因此稱為未檢查異常。該組還包括 RuntimeExceptions,它們是從Exception派生的異常類,由 JVM 在運行時生成。它們通常是由編程錯誤引起的。這些異常在編譯時也不會被檢查(未檢查),因此您不需要編寫代碼來處理它們。第二組包括在您編寫程序時可以預見的異常情況(因此您應該編寫代碼來處理它們)。此類異常稱為已檢查異常。說到異常,Java 開發人員的大部分工作就是處理這種情況。

創建異常

當程序運行時,JVM 或手動使用throw語句生成異常。發生這種情況時,會在內存中創建一個異常對象,程序的主流程被打斷,JVM 的異常處理程序會嘗試處理該異常。

異常處理

在 Java 中,我們創建代碼塊,在其中使用try{}catchtry{}catch{}finallytry{}finally{}結構 預測異常處理的需要。當tryJava 中的異常 - 4塊中拋出異常時,JVM 會在下一個catch塊中尋找合適的異常處理程序。如果catch塊具有所需的異常處理程序,則將控制權傳遞給它。如果沒有,則 JVM 會在catch塊鏈中向下查找,直到找到合適的處理程序。執行catch塊後,控制權將轉移到可選的finally塊。如果一個合適的捕獲沒有找到塊,然後 JVM 停止程序並顯示堆棧跟踪(當前方法調用堆棧),如果它存在,則首先執行finally塊。異常處理示例:

public class Print {

     void print(String s) {
        if (s == null) {
            throw new NullPointerException("Exception: s is null!");
        }
        System.out.println("Inside print method: " + s);
    }

    public static void main(String[] args) {
        Print print = new Print();
        List list= Arrays.asList("first step", null, "second step");

        for (String s : list) {
            try {
                print.print(s);
            }
            catch (NullPointerException e) {
                System.out.println(e.getMessage());
                System.out.println("Exception handled. The program will continue");
            }
            finally {
                System.out.println("Inside finally block");
            }
            System.out.println("The program is running...");
            System.out.println("-----------------");
        }

    }
    }
以下是主要方法 的結果:

Inside print method: first step
Inside finally block
The program is running...
-----------------
Exception: s is null!
Exception handled. The program will continue
Inside finally block
The program is running...
-----------------
Inside print method: second step
Inside finally block
The program is running...
-----------------
finally通常用於關閉任何流並釋放在try塊中打開/分配任何資源。然而,在編寫程序時,並不總是能夠跟踪所有資源的關閉情況。為了讓我們的生活更輕鬆,Java 的開發人員提供了try-with-resources結構,它會自動關閉在try塊中打開的任何資源。我們的第一個例子可以用try-with-resources重寫:

public String input() throws MyException {
    String s = null;
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
        s = reader.readLine();
   } catch (IOException e) {
       System.out.println(e.getMessage());
   }
    if (s.equals("")) {
        throw new MyException ("The string cannot be empty!");
    }
    return s;
}
得益於 Java 7 版本引入的能力,我們還可以將捕獲異構異常合併到一個塊中,使代碼更加緊湊和可讀。例子:

public String input() {
    String s = null;
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
        s = reader.readLine();
        if (s.equals("")) {
            throw new MyException("The string cannot be empty!");
        }
    } catch (IOException | MyException e) {
        System.out.println(e.getMessage());
    }
    return s;
}

底線

在 Java 中使用異常可以讓您通過創建“備份路由”使程序更健壯,使用 catch 塊將主要代碼與異常處理代碼分開,並使用 throws 將異常處理的責任轉移給使用您的方法的任何