介紹
異常是在程序執行期間可能發生並干擾其正常流程的錯誤事件。Java 提供了一種健壯且面向對象的方式來處理異常情況,稱為 Java 異常處理。
Java 中的異常可能源於不同類型的情況,例如用戶輸入錯誤的數據、硬件故障、網絡連接失敗或數據庫服務器宕機。指定在特定異常情況下該執行的代碼稱為異常處理。
拋出和捕獲異常
當執行語句時發生錯誤時,Java 會創建一個異常對象。異常對象包含大量的調試信息,如方法層次結構、發生異常的行號和異常類型。
如果在方法中發生異常,創建異常對象並將其交給運行時環境的過程稱為“拋出異常”。程序的正常流程停止,Java運行時環境(JRE)嘗試查找異常的處理程序。異常處理程序是可以處理異常對象的代碼塊。
- 查找異常處理程序的邏輯始於查找錯誤發生的方法。
- 如果找不到合適的處理程序,則將移至調用方法。
- 依此類推。
因此,如果方法的調用堆棧是A->B->C
,並且在方法C
中引發異常,則查找合適的處理程序將從C->B->A
移動。
如果找到合適的異常處理程序,則將異常對象傳遞給處理程序以處理它。該處理程序被稱為“捕獲異常”。如果找不到合適的異常處理程序,則程序將終止並將有關異常的信息打印到控制台。
Java異常處理框架僅用於處理運行時錯誤。編譯時錯誤必須由編寫代碼的開發人員修正,否則程序將不會執行。
Java異常處理關鍵字
Java 提供了專門用於處理異常的關鍵字。
- throw – 我們知道,如果發生錯誤,將會創建一個異常物件,然後 Java 運行時開始處理它們。有時我們可能希望在我們的代碼中明確生成異常。例如,在用戶身份驗證程序中,如果密碼為
null
,我們應該向客戶端拋出異常。使用throw
關鍵字將異常拋出到運行時以處理它。 - throws – 當我們在方法中拋出異常而未處理它時,我們必須在方法簽名中使用
throws
關鍵字,以讓調用程序知道該方法可能拋出的異常。調用方法可能處理這些異常或使用throws
關鍵字將其傳播給其調用方法。我們可以在throws
子句中提供多個異常,它也可以與main()
方法一起使用。 - try-catch – 我們在代碼中使用
try-catch
块來處理異常。try
是該塊的開始,而catch
是在try
块的末尾以處理異常。我們可以在一個try
块中有多個catch
块。try-catch
块也可以嵌套。catch
块需要一個參數,該參數應該是Exception
類型。 - 最终 –
finally
块是可选的,只能与try-catch
块一起使用。由于异常会中断执行过程,可能会导致一些资源未关闭,因此我们可以使用finally
块。无论是否发生异常,finally
块始终执行。
异常处理示例
testException()
方法使用throw
关键字抛出异常。方法签名使用throws
关键字通知调用者可能抛出的异常类型。- 在
main()
方法中,我使用try-catch
块处理异常。当我不处理时,我使用main()
方法中的throws
子句将其传播到运行时。 testException(-10)
由于异常而永远不会执行,然后执行finally
块。
printStackTrace()
是 Exception
类中用于调试的有用方法。
此代码将输出以下内容:
Outputjava.io.FileNotFoundException: Negative Integer -5
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
一些重要注意事项:
- 没有
try
语句的情况下,我们不能有catch
或finally
子句。 - A
try
statement should have eithercatch
block orfinally
block, it can have both blocks. - 我們無法在
try-catch-finally
塊之間寫任何代碼。 - 我們可以在單個
try
語句中使用多個catch
塊。 try-catch
塊可以嵌套,類似於if-else
語句。- 我們只能在
try-catch
語句中有一個finally
塊。
Java異常層次結構
正如之前所述,當引發異常時,會創建一個異常對象。 Java異常是層次結構的,並且使用繼承來將不同類型的異常進行分類。 Throwable
是Java異常層次結構的父類,它有兩個子對象 – Error
和Exception
。Exception
進一步分為已檢查的Exception
和運行時Exception
。
- 錯誤:
Error
是超出應用程序範圍的異常情況,不可能預測並從中恢復。例如,硬件故障,Java虛擬機(JVM)崩潰或內存不足錯誤。這就是為什麼我們有一個單獨的Error
層次結構,我們不應該嘗試處理這些情況。一些常見的Error
包括OutOfMemoryError
和StackOverflowError
。 - 檢查的異常: 檢查的
Exception
是我們可以預料到的程序中的異常情況,並試圖從中恢復。例如,FileNotFoundException
。我們應該捕獲此異常並向用戶提供有用的消息,並對其進行適當的記錄以進行調試。Exception
是所有檢查的Exception
的父類。如果我們正在拋出一個檢查的Exception
,我們必須在同一方法中catch
它,或者我們必須使用throws
關鍵字將其傳播給調用者。 - 運行時異常: 運行時
Exception
是由編程不良引起的。例如,試圖從數組中檢索元素。在嘗試檢索元素之前,我們應該先檢查數組的長度,否則它可能會在運行時拋出ArrayIndexOutOfBoundException
。RuntimeException
是所有運行時Exception
的父類。如果我們在方法中throw
任何運行時Exception
,則不需要在方法簽名throws
子句中指定它們。通過改進編程可以避免運行時異常。
異常類的一些有用方法
Java Exception
和它的所有子類別都沒有提供任何特定方法,所有方法都在基類 Throwable
中定義。Exception
類別被創建用於指定不同種類的 Exception
情境,以便我們可以輕鬆識別根本原因並根據其類型處理 Exception
。 Throwable
類實現了 Serializable
介面以實現互通性。
Throwable
類的一些有用方法為:
- public String getMessage() – 此方法返回
Throwable
的消息String
,並且該消息可以在創建異常時通過其構造函數提供。 - public String getLocalizedMessage() – 提供此方法以使子類別可以覆蓋它以向呼叫程序提供特定於地區的消息。該方法的
Throwable
類實現使用getMessage()
方法返回異常消息。 - public synchronized Throwable getCause() – 此方法返回異常的原因,如果原因未知則返回
null
。 - public String toString() – 此方法以
String
格式返回關於Throwable
的信息,返回的String
包含Throwable
類的名稱和本地化消息。 - public void printStackTrace() – 此方法將堆疊跟踪信息打印到標準錯誤流,該方法被重載,我們可以傳遞
PrintStream
或PrintWriter
作為參數將堆疊跟踪信息寫入文件或流。
Java 7自動資源管理和catch塊改進
如果您在單個try
塊中捕獲了許多異常,您會注意到catch
塊代碼主要由冗餘代碼組成以記錄錯誤。在Java 7中,一個功能是改進的catch
塊,我們可以在單個catch
塊中捕獲多個異常。這是具有此功能的catch
塊的示例:
存在一些限制,例如異常對象是final的,我們不能在catch
塊內部修改它,詳細分析請參閱Java 7 Catch Block Improvements。
大多數時候,我們使用finally
塊只是為了關閉資源。有時我們忘記關閉它們,當資源用完時會出現運行時異常。這些異常很難調試,我們可能需要查看使用該資源的每個地方,以確保我們正在關閉它。在Java 7中,一個改進是try-with-resources
,我們可以在try
語句中創建資源並在try-catch
塊內部使用它。當執行離開try-catch
塊時,運行時環境會自動關閉這些資源。這裡是帶有此改進的try-catch
塊的示例:
A Custom Exception Class Example
Java為我們提供了許多異常類別供我們使用,但有時我們可能需要創建自己的自定義異常類別。例如,通知調用方特定類型的異常並提供適當的消息。我們可以為追蹤定制字段,如錯誤碼。例如,假設我們編寫一個僅處理文本文件的方法,那麼當其他類型的文件作為輸入時,我們可以向調用方提供適當的錯誤碼。
首先,創建MyException
:
然後,創建CustomExceptionExample
:
我們可以有一個獨立的方法來處理從不同方法獲得的不同類型的錯誤碼。其中一些被消耗掉,因為我們可能不希望通知用戶,或者一些我們將它們拋回以通知用戶問題。
這裡我擴展了Exception
,以便每當產生此異常時,必須在方法中處理它或返回給呼叫者程式。如果我們擴展RuntimeException
,則不需要在throws
子句中指定它。
這是一個設計決策。使用Checked Exception
的好處是幫助開發人員了解可以預期和採取適當措施來處理它們的異常。
Java中異常處理的最佳實踐
- 使用特定的異常 – 異常層次結構的基類不提供任何有用的信息,這就是為什麼Java有這麼多異常類的原因,例如
IOException
,進一步的子類如FileNotFoundException
,EOFException
等。我們應該總是throw
和catch
特定的異常類,這樣呼叫者就能輕鬆知道異常的根本原因並處理它們。這使得調試更容易,並幫助客戶端應用程序適當地處理異常。 - 早點拋出或快速失敗 – 我們應該盡可能早地
throw
異常。考慮上面的processFile()
方法,如果我們將null
參數傳遞給此方法,我們將獲得以下異常:
OutputException in thread "main" java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:134)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
在调试过程中,我们必须仔细查看堆栈跟踪,以确定异常的实际位置。如果我们改变实现逻辑,并提前检查这些异常,如下所示:
那么异常堆栈跟踪将指示异常发生的位置,并提供清晰的消息:
Outputcom.journaldev.exceptions.MyException: File name can't be null
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
- Catch Late – 由于Java要求要么处理已检查的异常,要么在方法签名中声明它,有时开发人员倾向于
catch
异常并记录错误。但这种做法是有害的,因为调用程序不会收到任何有关异常的通知。我们只应在能够适当处理异常时才catch
异常。例如,在上述方法中,我正在将异常抛回给调用方的方法来处理。其他可能希望以不同方式处理异常的应用程序也可以使用相同的方法。在实现任何功能时,我们应始终将异常抛回给调用方,并让它们决定如何处理异常。 - 关闭资源 – 由于异常会中断程序的处理过程,我们应在finally块中关闭所有资源,或者使用Java 7的
try-with-resources
增强功能,让Java运行时为您关闭它。 - 记录异常 – 我们应始终记录异常消息,并在
throw
异常时提供清晰的消息,以便调用者能够轻松了解异常原因。我们应始终避免空的catch
块,它仅捕获异常而不提供任何有关异常的有意义的详细信息以进行调试。 - 单一catch块处理多个异常 – 大多数情况下,我们记录异常详细信息并向用户提供消息,在这种情况下,我们应使用Java 7的功能,在单个
catch
块中处理多个异常。这种方法将减小代码体积,看起来也更清晰。 - 使用自定义异常 – 在设计时定义异常处理策略总是更好的选择,而不是
throw
和catch
多个异常,我们可以创建一个带有错误代码的自定义异常,调用程序可以处理这些错误代码。同时,创建一个处理不同错误代码并使用它们的实用方法也是个好主意。 - 命名约定和打包 – 创建自定义异常时,请确保以
Exception
结尾,以便从名称本身就能明确它是一个异常类。此外,请确保像Java开发工具包(JDK)中那样对它们进行打包。例如,IOException
是所有IO操作的基本异常。 - 謹慎使用異常情況 – 異常情況成本高昂,有時根本無需拋出異常,我們可以向調用方程式返回一個布爾變量,以指示操作是否成功。這在操作是可選的情況下很有用,不希望程序因失敗而陷入困境。例如,在從第三方網絡服務更新數據庫中的股票報價時,如果連接失敗,我們可能希望避免拋出異常。
- 記錄拋出的異常情況 – 使用Javadoc
@throws
清楚地指定方法拋出的異常情況。這在為其他應用程序提供接口時非常有用。
結論
在本文中,您了解了Java中的異常處理。您了解了throw
和throws
。您還了解了try
(以及try-with-resources
)、catch
和finally
區塊。
Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java