網(wǎng)站制作_做網(wǎng)站_耐思智慧找客源免費(fèi)用哪個(gè)軟件好
在程序運(yùn)行時(shí),如果遇到問題(比如除以零、文件找不到等),程序會(huì)發(fā)生異常。異常就像是程序的“錯(cuò)誤提醒”,當(dāng)程序運(yùn)行中出錯(cuò)時(shí),它會(huì)停止,給出一個(gè)錯(cuò)誤信息。我們可以通過異常處理來控制這些錯(cuò)誤,避免程序崩潰。異常處理機(jī)制基于“try-catch-finally”語法。
1.異常的分類
-
編譯時(shí)異常(Checked Exception):這種異常是編譯器檢查到的,程序必須處理這種異常。比如,文件找不到時(shí),Java會(huì)要求你去處理這個(gè)問題。必須捕獲或聲明處理。
- 例子:
IOException
(文件操作時(shí)出現(xiàn)錯(cuò)誤)。
- 例子:
-
運(yùn)行時(shí)異常(Unchecked Exception):這種異常發(fā)生在程序運(yùn)行時(shí),通常是一些小錯(cuò)誤,程序員可以選擇是否處理它。比如,除以零就是一個(gè)運(yùn)行時(shí)異常。
RuntimeException
是 Java 中常見的運(yùn)行時(shí)異常,它是所有 運(yùn)行時(shí)異常(Unchecked Exceptions)類的父類。RuntimeException
及其子類的異常通常是在程序運(yùn)行時(shí)拋出的,而不像 Checked Exception(如IOException
、SQLException
等)那樣需要顯式地在方法簽名中聲明或捕獲。不需要顯式捕獲,但可以捕獲。下面第五點(diǎn)有關(guān)于RuntimeException的詳細(xì)介紹。
- 例子:
ArithmeticException
(除以零),NullPointerException
(空指針錯(cuò)誤)。
- 例子:
2.異常處理
Java提供了try-catch-finally
語法來處理異常。
2.1 try-catch:捕獲異常并處理
- try塊中包含可能會(huì)拋出異常的代碼。
- catch塊用于捕獲特定類型的異常。
try {int result = 10 / 0; // 可能會(huì)拋出 ArithmeticException } catch (ArithmeticException e) {System.out.println("除以零錯(cuò)誤:" + e.getMessage()); }
2.2 finally:
無論是否發(fā)生異常,finally
中的代碼總是會(huì)執(zhí)行。通常用于清理資源,比如關(guān)閉文件流、數(shù)據(jù)庫連接等。
try {// 可能發(fā)生異常的代碼
} catch (Exception e) {// 異常處理代碼
} finally {// 清理資源的代碼
}
示例:
import java.io.FileReader;
import java.io.IOException;public class FinallyExample {public static void main(String[] args) {FileReader file = null;try {// 嘗試打開文件并讀取file = new FileReader("test.txt"); // 假設(shè)文件存在int data = file.read();System.out.println((char) data);} catch (IOException e) {System.out.println("文件讀取失敗:" + e.getMessage());} finally {// 確保文件流總是關(guān)閉,即使發(fā)生了異常try {if (file != null) {file.close();System.out.println("文件已關(guān)閉。");}} catch (IOException e) {System.out.println("關(guān)閉文件時(shí)出錯(cuò):" + e.getMessage());}}}
}
/*
輸出:
文件已關(guān)閉。
*/
?解釋:
try
塊中我們嘗試打開一個(gè)文件并讀取其中的內(nèi)容。- 如果文件讀取過程中沒有出現(xiàn)異常,
finally
塊會(huì)確保文件流被關(guān)閉。 finally
塊中的代碼無論是否發(fā)生異常都會(huì)執(zhí)行,這對(duì)于資源管理(如關(guān)閉文件流、數(shù)據(jù)庫連接等)非常重要。
3.拋出異常
可以通過throw
關(guān)鍵字手動(dòng)拋出一個(gè)異常。通過throws
聲明方法可能拋出的異常。
3.1 throw:用來拋出一個(gè)異常對(duì)象。
throw new ArithmeticException("除以零異常");
3.2 throws:用來聲明方法可能拋出的異常。
public void myMethod() throws IOException {// 可能拋出IOException的方法
}
綜合使用示例:?
public class ThrowFinallyExample {public static void main(String[] args) {try {processFile("test.txt");} catch (IOException e) {System.out.println("捕獲到異常: " + e.getMessage());}}// 處理文件的方法,可能會(huì)拋出 IOExceptionpublic static void processFile(String fileName) throws IOException {try {System.out.println("開始處理文件:" + fileName);// 假設(shè)處理過程中發(fā)生了異常if (fileName == null) {throw new IOException("文件名不能為空!");}System.out.println("文件處理完成。");} catch (IOException e) {System.out.println("處理文件時(shí)發(fā)生異常:" + e.getMessage());throw e; // 將異常拋到外層} finally {// 這里模擬資源關(guān)閉,如果發(fā)生了錯(cuò)誤,就拋出異常System.out.println("資源清理中...");if (fileName == null) {throw new IOException("資源清理失敗:文件名為空!");}System.out.println("資源清理完成。");}}
}
/*
輸出:
開始處理文件:test.txt
資源清理中...
資源清理完成。
*/
解釋:
- 在
processFile
方法中,首先模擬文件處理過程中發(fā)生的IOException
。 catch
捕獲并處理該異常后,將其重新拋出,傳遞到方法外層。- 在
finally
塊中,無論是否發(fā)生異常,都執(zhí)行資源清理的代碼。如果清理過程中發(fā)生了問題,我們會(huì)拋出一個(gè)新的異常。
3.3 總結(jié):
throw
:用于顯式拋出異常。你可以在代碼中主動(dòng)拋出異常,以便在某些條件不滿足時(shí)提前中止執(zhí)行,提示錯(cuò)誤。finally
:用于保證無論是否發(fā)生異常,某些代碼都會(huì)執(zhí)行,通常用于清理工作,如關(guān)閉文件流、數(shù)據(jù)庫連接等資源。
throw
拋出的異常需要在try-catch
中進(jìn)行捕獲,或者通過throws
聲明拋出。finally
塊中的代碼始終會(huì)執(zhí)行,即使try
塊或catch
塊拋出異常。
4.常見的異常類
4.1 Throwable:是Java異常體系的根類,所有異常類的父類。
4.1.1?Error:
Error
主要指系統(tǒng)級(jí)錯(cuò)誤,通常不應(yīng)該嘗試捕獲這些錯(cuò)誤。因?yàn)橐坏┏霈F(xiàn)了 Error
類型的異常,程序通常會(huì)無法恢復(fù),例如 OutOfMemoryError
?或StackOverflowError
。在實(shí)際開發(fā)中,遇到這類錯(cuò)誤時(shí)通常是代碼本身或者運(yùn)行環(huán)境出現(xiàn)了問題,需要從根本上修復(fù),而不是捕獲異常后繼續(xù)執(zhí)行(即一般不需要捕獲,而需要你手動(dòng)把代碼改正確來)。
4.1.2 Exception:
Exception
是程序中的常見錯(cuò)誤,我們可以通過 try-catch
語句進(jìn)行捕獲和處理。常見的異常包括 IOException
、ArithmeticException
、NullPointerException
等。我們需要根據(jù)具體異常選擇合適的處理方式。
4.2 常見的異常類:
4.2.1 IOException:
輸入輸出操作異常,例如文件讀取時(shí)文件不存在或者無法讀取就會(huì)拋出IOException錯(cuò)誤。
import java.io.*;public class IOExceptionExample {public static void main(String[] args) {try {// 打開一個(gè)不存在的文件,會(huì)拋出 IOExceptionFileReader file = new FileReader("nonexistentfile.txt");BufferedReader reader = new BufferedReader(file);reader.read();reader.close();} catch (IOException e) {// 捕獲并處理 IOExceptionSystem.out.println("文件操作異常:" + e.getMessage());}}
}
解釋:代碼嘗試打開一個(gè)不存在的文件,導(dǎo)致拋出 IOException
。在 catch
塊中,我們捕獲該異常并打印錯(cuò)誤信息,而不是讓程序崩潰。
4.2.2 NullPointerException:
當(dāng)你試圖訪問或操作一個(gè) null
對(duì)象時(shí),JVM 無法執(zhí)行相關(guān)操作,因此拋出 NullPointerException
。這種異常通常會(huì)發(fā)生在以下幾種情況中:
- 試圖調(diào)用
null
引用的實(shí)例方法。 - 試圖訪問
null
引用的字段。 - 試圖獲取
null
引用的數(shù)組長(zhǎng)度。 - 試圖將
null
引用傳遞給需要非null
參數(shù)的方法。
public class NullPointerExceptionExample {public static void main(String[] args) {try {String str = null;System.out.println(str.length()); // str 為 null,會(huì)拋出 NullPointerException} catch (NullPointerException e) {// 捕獲并處理空指針異常System.out.println("發(fā)生了空指針異常: " + e.getMessage());}}
}
解釋:str
為 null
,調(diào)用 str.length()
會(huì)拋出 NullPointerException
。我們通過 try-catch
捕獲該異常,避免程序崩潰。
4.2.3 ArithmeticException:
算術(shù)運(yùn)算異常,例如當(dāng)你進(jìn)行除法操作時(shí),如果除數(shù)為零,程序會(huì)拋出 ArithmeticException
。
public class ArithmeticExceptionExample {public static void main(String[] args) {try {int result = 10 / 0; // 除以零,拋出 ArithmeticException} catch (ArithmeticException e) {// 捕獲并處理除零錯(cuò)誤System.out.println("發(fā)生了算術(shù)異常: " + e.getMessage());}}
}
解釋:除以零會(huì)拋出 ArithmeticException
,我們捕獲異常并輸出錯(cuò)誤信息,而不是讓程序崩潰。
4.2.4 ArrayIndexOutOfBoundsException:
當(dāng)你嘗試訪問一個(gè)數(shù)組時(shí),使用了無效的索引(即越界索引)時(shí),會(huì)拋出ArrayIndexOutOfBoundsException異常。
public class ArrayIndexOutOfBoundsExceptionExample {public static void main(String[] args) {int[] arr = new int[3]; // 創(chuàng)建一個(gè)長(zhǎng)度為 3 的數(shù)組try {// 嘗試訪問不存在的索引,數(shù)組長(zhǎng)度為 3,最大索引為 2arr[5] = 10; // 這會(huì)拋出 ArrayIndexOutOfBoundsException} catch (ArrayIndexOutOfBoundsException e) {System.out.println("發(fā)生了數(shù)組下標(biāo)越界異常: " + e.getMessage());}}
}
解釋:數(shù)組 arr
的長(zhǎng)度為 3,它的有效索引是 0, 1, 2
。但是我們嘗試訪問索引 5
,這是越界的,導(dǎo)致 ArrayIndexOutOfBoundsException
異常。通過 try-catch
語句捕獲異常,我們避免了程序崩潰,并輸出了錯(cuò)誤信息。
4.2.5 ClassNotFoundException:
ClassNotFoundException
是 Exception
類的子類,通常發(fā)生在 動(dòng)態(tài)加載類 時(shí),如果找不到指定的類,會(huì)拋出此異常。常見于使用反射、Class.forName()
或類加載器時(shí)。
發(fā)生的原因:
- 你試圖通過
Class.forName()
、ClassLoader.loadClass()
等方法動(dòng)態(tài)加載一個(gè)類,但該類在 classpath 中無法找到。 - 這通常發(fā)生在類路徑配置錯(cuò)誤,或者嘗試加載一個(gè)未編譯或缺失的類時(shí)。
public class ClassNotFoundExceptionExample {public static void main(String[] args) {try {// 使用 Class.forName 加載不存在的類Class.forName("com.example.NonExistentClass");} catch (ClassNotFoundException e) {System.out.println("發(fā)生了類未找到異常: " + e.getMessage());}} }
解釋:這里我們使用
Class.forName("com.example.NonExistentClass")
來加載一個(gè)不存在的類,這會(huì)拋出ClassNotFoundException
異常。通過try-catch
捕獲異常并輸出錯(cuò)誤信息,避免了程序崩潰。
解決方法:
- 確保類路徑配置正確,類文件已經(jīng)編譯并位于 classpath 下。
- 在動(dòng)態(tài)加載類之前,可以使用
ClassLoader
的getResource()
或getResourceAsStream()
等方法檢查類是否存在。ClassLoader classLoader = getClass().getClassLoader(); if (classLoader.getResource("com/example/NonExistentClass.class") != null) {// 類存在,可以加載Class.forName("com.example.NonExistentClass"); } else {System.out.println("類文件不存在!"); }
反射中使用 Class.forName()
通常情況下,我們會(huì)用反射動(dòng)態(tài)加載類,尤其是在類名只有在運(yùn)行時(shí)才能確定時(shí)。
public class ReflectionExample {public static void main(String[] args) {try {// 動(dòng)態(tài)加載一個(gè)類Class<?> clazz = Class.forName("java.util.ArrayList");System.out.println("加載成功: " + clazz.getName());} catch (ClassNotFoundException e) {System.out.println("類未找到: " + e.getMessage());}} }
解釋:
- 這段代碼成功加載了
java.util.ArrayList
類并打印出類的名稱。 - 如果
Class.forName()
中提供的類名無法找到,程序會(huì)拋出ClassNotFoundException
。
4.2.6 FileNotFoundException
(文件未找到異常):
FileNotFoundException
是一種輸入輸出異常,通常發(fā)生在你嘗試訪問一個(gè)不存在的文件時(shí)。- 比如,嘗試打開一個(gè)根本沒有的文件,就會(huì)拋出這個(gè)異常。
File file = new File("nonexistentfile.txt"); FileReader fr = new FileReader(file); // 如果文件不存在,會(huì)拋出 FileNotFoundException
如何避免?
-
在訪問文件時(shí),先檢查文件是否存在:
File file = new File("nonexistentfile.txt"); if (file.exists()) {FileReader fr = new FileReader(file); } else {System.out.println("文件不存在!"); }
4.2.7 SQLException
(SQL 異常): SQLException
是數(shù)據(jù)庫操作中常見的異常,通常發(fā)生在數(shù)據(jù)庫查詢失敗時(shí)。- 比如,執(zhí)行錯(cuò)誤的 SQL 查詢語句時(shí),會(huì)拋出這個(gè)異常。
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "user", "password"); Statement stmt = conn.createStatement(); stmt.executeUpdate("INVALID SQL QUERY"); // 會(huì)拋出 SQLException
如何避免?
- 在執(zhí)行 SQL 操作時(shí),確保 SQL 語句的正確性,并正確處理異常。
try {stmt.executeUpdate("SELECT * FROM users"); // 正確的 SQL 查詢 } catch (SQLException e) {System.out.println("SQL 執(zhí)行錯(cuò)誤:" + e.getMessage()); }
5.自定義異常
5.1 解釋:
可以根據(jù)需要定義自己的異常類,通常自定義異常類需要繼承Exception
或RuntimeException
。
class MyException extends Exception {public MyException(String message) {super(message);}
}public class Test {public static void test() throws MyException {throw new MyException("自定義異常");}public static void main(String[] args) {try {test();} catch (MyException e) {System.out.println("捕獲到自定義異常:" + e.getMessage());}}
}
5.2 關(guān)于RuntimeException:
5.2.1?RuntimeException
的基本介紹
- 繼承關(guān)系:
RuntimeException
繼承自Exception
類,并且是 未檢查異常(Unchecked Exception)的基類。 - 運(yùn)行時(shí)異常:運(yùn)行時(shí)異常通常表示程序的邏輯錯(cuò)誤或不合適的狀態(tài),開發(fā)者不需要強(qiáng)制捕獲這些異常。
- 不需要顯式聲明:與受檢查異常不同,
RuntimeException
不要求你在方法中使用throws
聲明它,且也不強(qiáng)制在代碼中進(jìn)行try-catch
捕獲。
5.2.2 常見的 RuntimeException
子類
NullPointerException
:訪問空對(duì)象引用時(shí)拋出的異常。ArithmeticException
:發(fā)生算術(shù)運(yùn)算錯(cuò)誤時(shí)拋出的異常,例如除以零。ArrayIndexOutOfBoundsException
:訪問數(shù)組時(shí),索引越界時(shí)拋出的異常。ClassCastException
:進(jìn)行不合法的類型轉(zhuǎn)換時(shí)拋出的異常。IllegalArgumentException
:當(dāng)方法接收到不合法的參數(shù)時(shí)拋出的異常。IllegalStateException
:方法被調(diào)用時(shí),當(dāng)前對(duì)象狀態(tài)不合法時(shí)拋出的異常。
5.2.3?RuntimeException
的特點(diǎn)
- 不需要捕獲:
RuntimeException
是未檢查異常,所以開發(fā)者不必在代碼中顯式捕獲它,也不必在方法簽名中聲明它。 - 通常是程序錯(cuò)誤:運(yùn)行時(shí)異常通常是因?yàn)榇a中的邏輯錯(cuò)誤或者數(shù)據(jù)錯(cuò)誤,比如訪問空指針、數(shù)組越界等。修復(fù)這些異常一般需要修改代碼邏輯。
5.2.4 如何使用 RuntimeException
雖然大多數(shù)情況下,RuntimeException
及其子類是由 JVM 自動(dòng)拋出的,但你也可以在自己的代碼中顯式拋出 RuntimeException
或其子類,來指示某種錯(cuò)誤。
拋出 RuntimeException
示例:
public class RuntimeExceptionExample {public static void main(String[] args) {try {checkAge(15); // 傳入一個(gè)非法的年齡值,拋出異常} catch (RuntimeException e) {System.out.println("捕獲到異常: " + e.getMessage());}}// 自定義方法,檢查年齡是否合法public static void checkAge(int age) {if (age < 18) {throw new IllegalArgumentException("年齡不能小于 18!");}System.out.println("年齡合格:" + age);}
}
/*
捕獲到異常: 年齡不能小于 18!
*/
解釋:
checkAge
方法中,如果傳入的年齡小于 18,則主動(dòng)拋出一個(gè)IllegalArgumentException
異常,表示年齡不合法。RuntimeException
的子類IllegalArgumentException
被拋出,并在catch
塊中捕獲和處理。
5.2.5 為什么使用 RuntimeException
RuntimeException
和其他未檢查異常(Unchecked Exception)通常用于:
- 表示代碼邏輯中的錯(cuò)誤,而不是外部條件導(dǎo)致的異常。
- 表示不容易預(yù)見或者不容易恢復(fù)的錯(cuò)誤,開發(fā)者可以通過修改代碼來避免這種錯(cuò)誤發(fā)生。
- 用于捕捉錯(cuò)誤輸入、錯(cuò)誤參數(shù)、程序不符合邏輯的狀態(tài)等情況。
5.2.6?RuntimeException
與其他異常的區(qū)別
特性 | RuntimeException | IOException 、SQLException 等 |
---|---|---|
檢查異常/非檢查異常 | 非檢查異常(Unchecked Exception) | 檢查異常(Checked Exception) |
是否強(qiáng)制捕獲 | 不強(qiáng)制捕獲或聲明 | 強(qiáng)制捕獲或聲明 |
拋出原因 | 程序邏輯錯(cuò)誤、非法操作 | 外部因素、資源不可用等 |
常見場(chǎng)景 | 空指針、除零、數(shù)組越界等 | 文件讀寫失敗、數(shù)據(jù)庫操作失敗等 |
5.2.7 何時(shí)使用 RuntimeException
你可以在以下情況使用 RuntimeException
:
- 不合法的參數(shù):當(dāng)方法的參數(shù)不符合預(yù)期時(shí)(如負(fù)數(shù)、空值等),你可以拋出一個(gè)
IllegalArgumentException
。 - 非法的狀態(tài):當(dāng)對(duì)象的狀態(tài)不適合調(diào)用某個(gè)方法時(shí),可以拋出一個(gè)
IllegalStateException
。 - 算術(shù)錯(cuò)誤:如除以零時(shí),拋出
ArithmeticException
。 - 不合理的類型轉(zhuǎn)換:如進(jìn)行不合法的類型強(qiáng)制轉(zhuǎn)換,拋出
ClassCastException
。
6.異常鏈
異常鏈(Exception Chaining)是指在捕獲異常時(shí),將原始異常作為另一個(gè)異常的原因(cause
)拋出。Java 提供了一種機(jī)制,允許我們?cè)趻伋鲂碌漠惓r(shí),把原本拋出的異常附加到新的異常中。這樣做可以幫助我們保留原始異常的詳細(xì)信息,方便后續(xù)調(diào)試和問題定位。
6.1?異常鏈的作用
異常鏈的主要作用是幫助我們追蹤問題的根源。當(dāng)我們捕獲到異常后,可以將其作為另一個(gè)異常的原因拋出,這樣就能保留原始異常的信息,便于定位問題的源頭。例如,捕獲一個(gè) SQLException
,并將其作為 IOException
的原因重新拋出,方便上層調(diào)用者了解到底是哪里出的問題。
6.2 異常鏈的基本使用
Java 提供了 Throwable
類的構(gòu)造方法,允許我們?cè)趻伋霎惓r(shí)指定一個(gè)原始的異常對(duì)象:
public Throwable(String message, Throwable cause)
public Throwable(String message, Throwable cause)
是 Java 中 Throwable
類的一個(gè)構(gòu)造方法,它用于創(chuàng)建一個(gè)帶有 錯(cuò)誤消息 和 原始異常 的異常對(duì)象。我們通常使用它來創(chuàng)建新的異常,同時(shí)保存引起該異常的原始原因。
6.2.1?構(gòu)造方法的參數(shù)說明
String message
:這個(gè)參數(shù)是一個(gè) 錯(cuò)誤消息,用于描述當(dāng)前異常的具體情況。通常是一個(gè)簡(jiǎn)短的字符串,用來說明異常的原因或上下文。例如:"File not found"
。Throwable cause
:這個(gè)參數(shù)是另一個(gè)異常對(duì)象,表示 導(dǎo)致當(dāng)前異常的原始異常。它通常是一個(gè)已經(jīng)存在的異常對(duì)象,我們把它傳遞到新的異常中來形成 異常鏈。
6.2.2?構(gòu)造函數(shù)的作用
- 當(dāng)我們?cè)诔绦蛑胁东@到一個(gè)異常,并且想要拋出一個(gè)新的異常時(shí),可以通過這個(gè)構(gòu)造函數(shù)將 原始異常(
cause
)傳遞給新異常。 - 這樣做的目的是保留 原始異常 的信息,幫助開發(fā)者追蹤錯(cuò)誤的根源。它是一種 異常鏈 技術(shù),可以讓我們知道一個(gè)異常是如何引發(fā)其他異常的。
6.2.3?為什么使用這個(gè)構(gòu)造函數(shù)?
使用這個(gè)構(gòu)造函數(shù)的好處是:
- 我們可以 拋出新的異常,并且 保留原始異常的上下文信息,從而有助于追蹤問題的根源。
- 通過
getCause()
方法,后續(xù)的異常處理者能夠獲取到原始異常并進(jìn)行處理。
6.2.4?例子:如何使用 Throwable(String message, Throwable cause)
假設(shè)我們?cè)谔幚硪粋€(gè)文件操作時(shí)發(fā)生了異常,我們可以通過異常鏈來傳遞原始的 IOException
異常,使得上層調(diào)用者能夠獲取到詳細(xì)的錯(cuò)誤信息。
代碼示例:
public class ExceptionChainingDemo {public static void main(String[] args) {try {processFile();} catch (Exception e) {e.printStackTrace();}}public static void processFile() throws Exception {try {openFile();} catch (Exception e) {// 將原始異常 e 包裝到一個(gè)新的異常中并拋出throw new Exception("Failed to process the file", e);}}public static void openFile() throws Exception {// 模擬拋出文件操作異常throw new java.io.IOException("File not found");}
}
輸出:
java.lang.Exception: Failed to process the fileat ExceptionChainingDemo.processFile(ExceptionChainingDemo.java:9)at ExceptionChainingDemo.main(ExceptionChainingDemo.java:4)
Caused by: java.io.IOException: File not foundat ExceptionChainingDemo.openFile(ExceptionChainingDemo.java:15)at ExceptionChainingDemo.processFile(ExceptionChainingDemo.java:7)... 1 more
6.2.5?解釋
- 在上面的代碼中:
openFile()
方法拋出了一個(gè)IOException
異常(模擬文件未找到的情況)。- 在
processFile()
方法中,我們捕獲了這個(gè)IOException
異常,并且通過new Exception("Failed to process the file", e)
創(chuàng)建了一個(gè)新的Exception
異常。 - 這里的
"Failed to process the file"
是新的異常的描述信息,而e
(即原始的IOException
異常)被傳遞作為 原始異常(即cause
)。
- 當(dāng)我們打印異常信息時(shí),
printStackTrace()
顯示了當(dāng)前異常的信息,并且通過Caused by
顯示了引發(fā)當(dāng)前異常的原始異常。
?
6.2.6 使用 Exception
和 RuntimeException
創(chuàng)建異常鏈
Exception
類和 RuntimeException
類都提供了帶有 cause
參數(shù)的構(gòu)造函數(shù),因此你可以在拋出這兩種異常時(shí)都使用異常鏈。
示例:創(chuàng)建 RuntimeException
異常鏈
public class RuntimeExceptionChaining {public static void main(String[] args) {try {method1();} catch (RuntimeException e) {// 捕獲并輸出異常信息,顯示異常鏈System.out.println("Caught exception: " + e);Throwable cause = e.getCause();if (cause != null) {System.out.println("Cause: " + cause);}}}public static void method1() {try {method2();} catch (RuntimeException e) {// 捕獲 method2 中的異常,并將其作為 method1 的原因throw new RuntimeException("Error occurred in method1", e);}}public static void method2() {// 模擬拋出一個(gè) RuntimeExceptionthrow new RuntimeException("An error occurred in method2");}
}
輸出:
Caught exception: java.lang.RuntimeException: Error occurred in method1
Cause: java.lang.RuntimeException: An error occurred in method2
6.2.7 為什么要使用異常鏈?
-
保留原始異常信息:當(dāng)我們?cè)诓东@到一個(gè)異常后,可以將它附加到一個(gè)新的異常中,這樣上層代碼就可以通過
getCause()
方法查看到原始的異常信息,幫助定位問題。 -
幫助追蹤錯(cuò)誤的根源:異常鏈可以讓我們清楚地看到異常是如何傳播的,特別是在復(fù)雜的系統(tǒng)中,錯(cuò)誤可能從底層層級(jí)一直傳遞到上層應(yīng)用,使用異常鏈可以更容易追蹤整個(gè)錯(cuò)誤過程。
-
提高代碼可讀性:通過異常鏈,能夠避免在捕獲異常后丟失原始的錯(cuò)誤信息,增強(qiáng)代碼的可讀性和可維護(hù)性。
6.2.8?getCause()
和 printStackTrace()
getCause()
:可以用來獲取原始異常(如果存在的話)。printStackTrace()
:會(huì)打印當(dāng)前異常以及異常鏈中的所有異常。
6.2.8.1?getCause()
方法
getCause()
方法用于獲取當(dāng)前異常的原始異常(即引發(fā)當(dāng)前異常的異常)。它是 Throwable
類的一部分,所有異常類(包括 Exception
和 Error
)都繼承自 Throwable
,因此都可以使用該方法。
1. 功能:
getCause()
返回的是一個(gè)Throwable
對(duì)象,它代表的是導(dǎo)致當(dāng)前異常發(fā)生的原始異常(如果存在的話)。- 如果當(dāng)前異常是直接拋出的,沒有原始異常,則返回
null
。
2. 示例:
public class GetCauseExample {public static void main(String[] args) {try {method1();} catch (Exception e) {// 獲取并打印原始異常System.out.println("Caught exception: " + e.getMessage());if (e.getCause() != null) {System.out.println("Cause: " + e.getCause());}}}public static void method1() throws Exception {try {method2();} catch (Exception e) {// 捕獲異常并將其作為原因拋出throw new Exception("Error in method1", e);}}public static void method2() throws Exception {// 模擬拋出一個(gè)異常throw new Exception("Error in method2");}
}
輸出:
Caught exception: Error in method1
Cause: java.lang.Exception: Error in method2
3.?解釋:
method2()
拋出了一個(gè)異常:"Error in method2"。method1()
捕獲了該異常,并將它作為原因(cause
)拋出了一個(gè)新的異常:"Error in method1"。- 在
main()
方法中,我們捕獲了這個(gè)新的異常,并通過getCause()
方法獲取到原始的異常信息。
6.2.8.2?printStackTrace()
方法
printStackTrace()
是 Throwable
類的一個(gè)方法,它用于打印異常的堆棧跟蹤信息。堆棧跟蹤信息通常包含以下內(nèi)容:
- 異常的類型和消息。
- 異常發(fā)生時(shí)的方法調(diào)用棧(即方法的調(diào)用路徑)。
- 異常發(fā)生的具體位置(行號(hào)和類名)。
1. 功能:
printStackTrace()
方法會(huì)將異常的堆棧信息輸出到控制臺(tái),幫助開發(fā)人員了解異常發(fā)生的詳細(xì)上下文。
2. 示例:
public class PrintStackTraceExample {public static void main(String[] args) {try {method1();} catch (Exception e) {// 打印異常的堆棧信息e.printStackTrace();}}public static void method1() throws Exception {try {method2();} catch (Exception e) {// 捕獲異常并將其作為原因拋出throw new Exception("Error in method1", e);}}public static void method2() throws Exception {// 模擬拋出一個(gè)異常throw new Exception("Error in method2");}
}
輸出:
java.lang.Exception: Error in method1at PrintStackTraceExample.method1(PrintStackTraceExample.java:10)at PrintStackTraceExample.main(PrintStackTraceExample.java:5)
Caused by: java.lang.Exception: Error in method2at PrintStackTraceExample.method2(PrintStackTraceExample.java:16)at PrintStackTraceExample.method1(PrintStackTraceExample.java:8)... 1 more
3.?解釋:
- 在
method2()
中,我們拋出了一個(gè)異常"Error in method2"
,然后在method1()
中捕獲該異常并將其作為原因拋出了新的異常"Error in method1"
。 - 當(dāng)在
main()
中捕獲到異常并調(diào)用printStackTrace()
時(shí),異常信息不僅顯示當(dāng)前異常,還顯示了由原始異常引起的Caused by
部分。這樣,我們可以清晰地看到異常鏈,追蹤錯(cuò)誤的根本原因。
7.異常的最佳實(shí)踐
- 捕獲特定異常:盡量捕獲具體的異常,而不是捕獲
Exception
。 - 不要忽略異常:避免捕獲異常后什么都不做,這會(huì)隱藏程序中的問題。
- 及時(shí)釋放資源:在
finally
中關(guān)閉文件流、數(shù)據(jù)庫連接等資源,確保資源能夠正確釋放。 - 使用自定義異常:在合適的情況下,定義并拋出自定義異常,提供更加具體的錯(cuò)誤信息。
8.異常的傳遞
在Java中,異??梢栽诜椒▋?nèi)部被捕獲并處理,也可以向上傳遞。異常的傳遞是通過方法聲明中的throws
來實(shí)現(xiàn)的。如果方法中拋出了異常且該異常沒有被處理,Java虛擬機(jī)會(huì)將其傳遞給調(diào)用該方法的地方。
8.1 異常傳播:
8.1.1 解釋:
如果一個(gè)方法拋出一個(gè)異常,而該方法的調(diào)用者沒有處理(即沒有捕獲或聲明throws
),這個(gè)異常將會(huì)被繼續(xù)拋出,直到它被某個(gè)方法捕獲或最終未被捕獲而導(dǎo)致程序終止。
public void methodA() throws Exception {methodB(); // methodB 可能拋出異常
}public void methodB() throws Exception {throw new Exception("Something went wrong");
}
?異常的多層次處理: 在多層方法調(diào)用中,如果外層方法沒有處理異常,內(nèi)層方法拋出的異常就會(huì)一直向上傳遞。
try {methodA(); // methodA 中會(huì)拋出異常
} catch (Exception e) {System.out.println("異常被捕獲:" + e.getMessage());
}
8.1.2 具體代碼舉例:
public class ExceptionHandlingExample {// methodA 拋出 Exceptionpublic void methodA() throws Exception {System.out.println("In methodA");methodB(); // 調(diào)用 methodB,methodB 可能拋出異常}// methodB 拋出一個(gè)異常public void methodB() throws Exception {System.out.println("In methodB");// 模擬拋出異常throw new Exception("Something went wrong in methodB");}public static void main(String[] args) {ExceptionHandlingExample example = new ExceptionHandlingExample();try {example.methodA(); // 調(diào)用 methodA,methodA 中會(huì)調(diào)用 methodB,methodB 拋出異常} catch (Exception e) {// 捕獲異常并處理System.out.println("異常被捕獲: " + e.getMessage()); // 打印異常消息e.printStackTrace(); // 打印異常的堆棧信息}}
}
8.1.3 代碼說明:
: methodA
methodA
聲明throws Exception
,意味著它會(huì)拋出Exception
類型的異常。- 在
methodA
中,我們調(diào)用了methodB()
,而methodB
可能會(huì)拋出一個(gè)異常。
methodB
:
methodB
也聲明了 throws Exception
,表示該方法可能會(huì)拋出異常。
在 methodB
中,我們模擬拋出了一個(gè) Exception
,并傳遞了錯(cuò)誤消息 "Something went wrong in methodB"
。
main
方法:
- 在
main
方法中,我們創(chuàng)建了ExceptionHandlingExample
的實(shí)例,并調(diào)用methodA()
。 methodA()
調(diào)用methodB()
,而methodB()
會(huì)拋出一個(gè)異常,因此methodA()
也會(huì)拋出異常。- 在
try
塊中,我們捕獲了methodA()
拋出的異常,使用catch
語句塊處理異常。 - 我們使用
e.getMessage()
打印異常的消息,并使用e.printStackTrace()
打印異常的堆棧跟蹤信息。
輸出結(jié)果:
In methodA
In methodB
異常被捕獲: Something went wrong in methodB
java.lang.Exception: Something went wrong in methodBat ExceptionHandlingExample.methodB(ExceptionHandlingExample.java:17)at ExceptionHandlingExample.methodA(ExceptionHandlingExample.java:9)at ExceptionHandlingExample.main(ExceptionHandlingExample.java:27)
解釋:
- 程序首先進(jìn)入
methodA
,然后調(diào)用methodB
。 methodB
拋出了一個(gè)異常"Something went wrong in methodB"
,并且異常被methodA
捕獲。methodA
繼續(xù)拋出該異常,最終在main
方法中的try-catch
塊中捕獲到這個(gè)異常。- 異常的消息
"Something went wrong in methodB"
被打印出來,且e.printStackTrace()
打印了詳細(xì)的堆棧信息,顯示異常是如何從methodB
傳遞到methodA
的。
9.捕獲多個(gè)異常
9.1 解釋:
Java 7 引入了多重異常捕獲(Multi-catch),允許在一個(gè) catch
塊中捕獲多個(gè)異常,這樣可以減少重復(fù)的代碼,并提高代碼的可讀性。你只需要在 catch
塊中的異常類型之間使用 |
(管道符)進(jìn)行分隔。
9.2 代碼格式:
try {// 可能發(fā)生異常的代碼
} catch (IOException | SQLException e) { // 捕獲多種異常System.out.println("發(fā)生異常:" + e.getMessage());
}
9.3 要求:
- 多個(gè)異常類必須有共同的父類,通常是
Exception
或其子類(例如:IOException
和SQLException
都是Exception
的子類)。 - 在
catch
塊中,捕獲的異常類的對(duì)象(這里是e
)會(huì)變成Throwable
的父類,因此你不能再對(duì)e
進(jìn)行多態(tài)特有的方法調(diào)用。
9.4 具體例子:
假設(shè)我們有兩個(gè)異常:IOException
(輸入輸出異常)和 SQLException
(SQL 異常)。我們會(huì)模擬一個(gè)程序,在操作文件和數(shù)據(jù)庫時(shí)分別拋出這兩個(gè)異常,并使用 Java 7 的多重異常捕獲來處理。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;public class MultiCatchExample {// 模擬讀取文件public static void readFile() throws IOException {// 模擬文件未找到異常throw new FileNotFoundException("文件未找到");}// 模擬數(shù)據(jù)庫操作public static void connectToDatabase() throws SQLException {// 模擬SQL異常throw new SQLException("數(shù)據(jù)庫連接失敗");}public static void main(String[] args) {try {readFile(); // 可能拋出 IOExceptionconnectToDatabase(); // 可能拋出 SQLException} catch (IOException | SQLException e) { // 捕獲多種異常System.out.println("發(fā)生異常:" + e.getMessage());}}
}
9.4.1 代碼解釋:
-
readFile()
方法:- 該方法模擬讀取文件的操作,可能會(huì)拋出
IOException
類型的異常。為了演示,我們使用FileNotFoundException
(它是IOException
的子類)來模擬文件未找到的異常。
- 該方法模擬讀取文件的操作,可能會(huì)拋出
-
connectToDatabase()
方法:- 該方法模擬連接數(shù)據(jù)庫的操作,可能會(huì)拋出
SQLException
類型的異常。我們直接拋出一個(gè)SQLException
。
- 該方法模擬連接數(shù)據(jù)庫的操作,可能會(huì)拋出
-
main()
方法:- 我們?cè)?
try
塊中依次調(diào)用readFile()
和connectToDatabase()
方法,這兩個(gè)方法都有可能拋出異常。 - 在
catch
塊中,我們使用|
操作符捕獲了IOException
和SQLException
,并通過e.getMessage()
打印了異常消息。
- 我們?cè)?
9.4.2 輸出結(jié)果:
發(fā)生異常:文件未找到
9.5 總結(jié):
- 多重異常捕獲:Java 7 引入了多重異常捕獲,允許你在同一個(gè)
catch
塊中捕獲多個(gè)異常。你只需使用|
分隔異常類,如IOException | SQLException
。 - 減少重復(fù)代碼:這種方式讓你避免了為每個(gè)異常寫一個(gè)
catch
塊的冗余代碼,從而使代碼更簡(jiǎn)潔、可讀性更強(qiáng)。 - 共同父類:多個(gè)異常類必須有共同的父類,通常是
Exception
或其子類,否則不能進(jìn)行多重異常捕獲。
10.異常的性能
異常處理會(huì)影響程序的性能,尤其是在頻繁拋出異常的情況下。為了優(yōu)化性能,應(yīng)該避免在正常的程序流程中使用異常。例如,不應(yīng)該使用異常來控制程序流程,尤其是在循環(huán)或頻繁執(zhí)行的代碼塊中。
-
異常的成本: 拋出異常是一個(gè)相對(duì)昂貴的操作,因?yàn)樗枰獎(jiǎng)?chuàng)建異常對(duì)象并進(jìn)行堆棧跟蹤。因此,最好在必要時(shí)才拋出異常。
-
異常的優(yōu)化:
- 避免過多的
try-catch
塊,尤其是在循環(huán)中。 - 捕獲異常的塊應(yīng)盡量簡(jiǎn)短,不要做復(fù)雜的邏輯處理。
- 避免過多的
11.異常的嵌套與多線程中的異常處理
-
嵌套異常: 異??赡軙?huì)嵌套。例如,一個(gè)方法拋出的異常被另一個(gè)方法捕獲并進(jìn)一步拋出,這樣形成了嵌套異常鏈??梢酝ㄟ^
getCause()
方法獲取引起當(dāng)前異常的根本原因。try {throw new IOException("File not found"); } catch (IOException e) {throw new RuntimeException("Failed to read file", e); // 將 IOException 作為 RuntimeException 的根本原因 }
使用
e.getCause()
可以獲取到原始的異常對(duì)象,從而追蹤到真正的錯(cuò)誤源。 -
多線程中的異常處理: 在多線程編程中,異常處理稍顯復(fù)雜。每個(gè)線程都有自己的執(zhí)行棧,因此在每個(gè)線程中都可能發(fā)生異常。Java提供了
Thread.UncaughtExceptionHandler
接口來處理未捕獲的線程異常。Thread thread = new Thread(() -> {// 可能拋出異常的代碼 }); thread.setUncaughtExceptionHandler((t, e) -> {System.out.println("線程 " + t.getName() + " 拋出了異常:" + e.getMessage()); }); thread.start();
通過設(shè)置未捕獲異常處理器,我們可以對(duì)線程中的異常進(jìn)行集中處理,而不會(huì)讓整個(gè)應(yīng)用崩潰。
12.Java 8 引入的異常流處理
在Java 8中,引入了流式API(Stream API),這使得在進(jìn)行流操作時(shí),異常處理變得更加重要。流中的方法(如map()
、filter()
等)通常要求無異常的輸入,但是你可能會(huì)遇到需要在流中處理異常的場(chǎng)景。
一種常見的方式是通過try-catch
包裝流中的異常:
List<String> data = Arrays.asList("1", "2", "abc", "4");List<Integer> result = data.stream().map(str -> {try {return Integer.parseInt(str);} catch (NumberFormatException e) {return null; // 處理異常,返回null}}).filter(Objects::nonNull).collect(Collectors.toList());System.out.println(result); // 輸出:[1, 2, 4]
在這種情況下,我們使用了map()
來處理每個(gè)元素可能發(fā)生的NumberFormatException
異常,并返回null
值,最后通過filter()
去掉null
值。
13.資源管理與自動(dòng)關(guān)閉(Java 7引入的AutoCloseable)
從Java 7開始,引入了自動(dòng)資源管理(ARM),即try-with-resources
語句,專門用于處理需要關(guān)閉的資源(如文件、數(shù)據(jù)庫連接等)。資源實(shí)現(xiàn)了AutoCloseable
接口,保證無論是否發(fā)生異常,都會(huì)自動(dòng)關(guān)閉資源。
try (FileReader fr = new FileReader("file.txt")) {// 讀取文件
} catch (IOException e) {e.printStackTrace();
} // FileReader 會(huì)在此自動(dòng)關(guān)閉,無論是否發(fā)生異常
這種方式能夠確保即使在異常發(fā)生時(shí),資源也能正確地被釋放,避免了資源泄漏問題。