1.異常類型
所有的異常分為4種類型,它們實際上是相互繼承的類。
Throwable
班級
所有異常的基類是類Throwable
。該類Throwable
包含將當前調用堆棧(當前方法的堆棧跟踪)寫入數組的代碼。稍後我們將了解堆棧跟踪是什麼。
throw運算符只能接受從Throwable
類派生的對象。儘管理論上您可以編寫類似 的代碼throw new Throwable();
,但通常沒有人這樣做。該類的主要目的Throwable
是為所有異常提供一個父類。
Error
班級
下一個異常類是Error
類,直接繼承Throwable
類。當出現嚴重問題時, Java 機器會創建該類Error
(及其後代)的對象。例如,硬件故障、內存不足等。
通常,作為程序員,在程序中出現這樣的錯誤(應該拋出an 的那種)時,您無能為力:這些錯誤太嚴重了。Error
您所能做的就是通知用戶程序正在崩潰和/或將有關錯誤的所有已知信息寫入程序日誌。
Exception
班級
和Exception
類RuntimeException
是針對很多方法運行中出現的常見錯誤。每個拋出的異常的目標是被知道如何正確處理它的塊捕獲。catch
當一個方法由於某種原因不能完成它的工作時,它應該立即通過拋出適當類型的異常來通知調用方法。
換句話說,如果一個變量等於null
,該方法將拋出一個NullPointerException
。如果將不正確的參數傳遞給該方法,它將拋出一個InvalidArgumentException
. 如果該方法不小心被零除,它會拋出一個ArithmeticException
.
RuntimeException
班級
RuntimeExceptions
是 的一個子集Exceptions
。我們甚至可以說這RuntimeException
是普通異常 ( ) 的輕量級版本Exception
——對此類異常施加的要求和限制更少
Exception
稍後您將了解 和 之間的區別RuntimeException
。
2. Throws
:檢查異常
所有 Java 異常都分為兩類:已檢查和未檢查。
所有繼承RuntimeException
or的異常Error
都被認為是未經檢查的異常。 所有其他都是已檢查的異常。
在引入檢查異常 20 年後,幾乎每個 Java 程序員都認為這是一個錯誤。在流行的現代框架中,95% 的異常都是未經檢查的。幾乎一模一樣照抄Java的C#語言,並沒有加入checked exceptions。
已檢查異常和未檢查異常之間的主要區別是什麼?
對已檢查的異常有額外的要求。粗略地說,它們是這些:
要求 1
如果一個方法拋出一個已檢查的異常,它必須在它的簽名中指明異常的類型。這樣,調用它的每個方法都知道這個“有意義的異常”可能會發生在其中。
在關鍵字後的方法參數後面指明檢查異常(不要誤用關鍵字)。它看起來像這樣:throws
throw
type method (parameters) throws exception
例子:
檢查異常 | 未經檢查的異常 |
---|---|
|
|
在右邊的例子中,我們的代碼拋出了一個未經檢查的異常——不需要額外的操作。 在左側的示例中,該方法拋出一個已檢查的異常,因此throws
將關鍵字與異常類型一起添加到方法簽名中。
如果一個方法希望拋出多個已檢查的異常,則必須在關鍵字後指定所有這些異常throws
,並以逗號分隔。順序並不重要。例子:
public void calculate(int n) throws Exception, IOException
{
if (n == 0)
throw new Exception("n is null!");
if (n == 1)
throw new IOException("n is 1");
}
要求 2
如果您調用的方法在其簽名中包含已檢查的異常,則您不能忽略它拋出異常的事實。
您必須通過catch
為每個異常添加塊來捕獲所有此類異常,或者將它們添加到您的方法的throws
子句中。
就好像我們在說,“這些異常非常重要,我們必須捕獲它們。如果我們不知道如何處理它們,那麼必須通知任何可能調用我們方法的人,其中可能會發生此類異常。
例子:
想像一下,我們正在編寫一種方法來創建一個由人類居住的世界。初始人數作為參數傳遞。所以如果人數太少,我們需要添加例外。
創造地球 | 筆記 |
---|---|
|
該方法可能會拋出兩個已檢查的異常:
|
此方法調用可以通過 3 種方式處理:
1.不要捕獲任何異常
當方法不知道如何正確處理這種情況時,通常會這樣做。
代碼 | 筆記 |
---|---|
|
調用方法不捕獲異常並且必須將它們通知其他人:它將它們添加到自己的throws 子句中 |
2.捕捉一些異常
我們處理我們可以處理的錯誤。但是我們不理解的,我們把它們丟給調用方法。為此,我們需要將他們的名字添加到 throws 子句中:
代碼 | 筆記 |
---|---|
|
調用者只捕獲一個已檢查的異常—— LonelyWorldException . 另一個異常必須添加到它的簽名中,在throws 關鍵字之後表明它 |
3.捕獲所有異常
如果該方法沒有向調用方法拋出異常,那麼調用方法總是確信一切正常。並且它將無法採取任何行動來修復異常情況。
代碼 | 筆記 |
---|---|
|
所有異常都在此方法中捕獲。來電者將確信一切順利。 |
3.包裝異常
受檢異常在理論上看起來很酷,但在實踐中卻是一個巨大的挫折。
假設你的項目中有一個超級流行的方法。它從您程序中的數百個地方調用。你決定向它添加一個新的檢查異常。很可能這個已檢查的異常真的很重要而且很特別,只有main()
方法知道如果它被捕獲時該怎麼做。
這意味著您必須將已檢查的異常添加到throws
調用您的超級流行方法的每個方法的子句中。以及在throws
調用這些方法的所有方法的子句中。以及調用這些方法的方法。
結果,throws
項目中一半方法的子句得到了一個新的檢查異常。當然,您的項目包含在測試中,現在測試無法編譯。現在您還必須在測試中編輯 throws 子句。
然後你的所有代碼(數百個文件中的所有更改)都必須由其他程序員審查。在這一點上,我們問自己為什麼要對項目進行如此多的血腥更改?一天(幾?)的工作和失敗的測試——都是為了添加一個檢查異常?
當然,仍然存在與繼承和方法重寫相關的問題。檢查異常帶來的問題比好處大得多。歸根結底,現在很少有人喜歡它們,也很少有人使用它們。
然而,仍然有很多代碼(包括標準 Java 庫代碼)包含這些已檢查的異常。他們該怎麼辦?我們不能忽視它們,我們也不知道如何處理它們。
Java 程序員建議將已檢查的異常包裝在RuntimeException
. 換句話說,捕獲所有已檢查的異常,然後創建未檢查的異常(例如,RuntimeException
)並拋出它們。這樣做看起來像這樣:
try
{
// Code where a checked exception might occur
}
catch(Exception exp)
{
throw new RuntimeException(exp);
}
這不是一個非常漂亮的解決方案,但這裡沒有任何犯罪行為:異常只是被塞進了一個RuntimeException
.
如果需要,您可以從那裡輕鬆檢索它。例子:
代碼 | 筆記 |
---|---|
|
獲取存儲在對象內部的異常 RuntimeException 。該cause 變量可能會null 確定其類型並將其轉換為已檢查的異常類型。 |
4.捕獲多個異常
程序員真的很討厭重複代碼。他們甚至提出了相應的開發原則:Don't Repeat Yourself (DRY)。但是在處理異常時,經常會try
出現一個塊後面跟著幾個catch
具有相同代碼的塊的情況。
或者可能有 3 個catch
塊具有相同的代碼,另外 2 個catch
塊具有其他相同的代碼。當您的項目負責任地處理異常時,這是一種標準情況。
從版本 7 開始,在 Java 語言中添加了在單個塊中指定多種異常類型的功能catch
。它看起來像這樣:
try
{
// Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
// Exception handling code
}
您可以擁有catch
任意數量的塊。但是,單個catch
塊不能指定相互繼承的異常。也就是說不能寫catch( Exception
| RuntimeException
e),因為RuntimeException
類繼承了Exception
。
5.自定義異常
您始終可以創建自己的異常類。您只需創建一個繼承該類的類RuntimeException
。它看起來像這樣:
class ClassName extends RuntimeException
{
}
我們將在您學習 OOP、繼承、構造函數和方法覆蓋時討論細節。
然而,即使你只有一個像這樣的簡單類(完全沒有代碼),你仍然可以基於它拋出異常:
代碼 | 筆記 |
---|---|
|
拋出一個未經檢查的 MyException . |
在Java 多線程任務中,我們將深入研究如何處理我們自己的自定義異常。
GO TO FULL VERSION