1.異常類型

異常類型

所有的異常分為4種類型,它們實際上是相互繼承的類。

Throwable班級

所有異常的基類是類Throwable該類Throwable包含將當前調用堆棧(當前方法的堆棧跟踪)寫入數組的代碼。稍後我們將了解堆棧跟踪是什麼。

throw運算符只能接受從Throwable類派生的對象。儘管理論上您可以編寫類似 的代碼throw new Throwable();,但通常沒有人這樣做。該類的主要目的Throwable是為所有異常提供一個父類。

Error班級

下一個異常類是Error類,直接繼承Throwable類。當出現嚴重問題時, Java 機器會創建該類Error(及其後代)的對象。例如,硬件故障、內存不足等。

通常,作為程序員,在程序中出現這樣的錯誤(應該拋出an 的那種)時,您無能為力:這些錯誤太嚴重了。Error您所能做的就是通知用戶程序正在崩潰和/或將有關錯誤的所有已知信息寫入程序日誌。

Exception班級

ExceptionRuntimeException是針對很多方法運行中出現的常見錯誤。每個拋出的異常的目標是被知道如何正確處理它的塊捕獲。catch

當一個方法由於某種原因不能完成它的工作時,它應該立即通過拋出適當類型的異常來通知調用方法。

換句話說,如果一個變量等於null,該方法將拋出一個NullPointerException。如果將不正確的參數傳遞給該方法,它將拋出一個InvalidArgumentException. 如果該方法不小心被零除,它會拋出一個ArithmeticException.

RuntimeException班級

RuntimeExceptions是 的一個子集Exceptions。我們甚至可以說這RuntimeException是普通異常 ( ) 的輕量級版本Exception——對此類異常施加的要求和限制更少

Exception稍後您將了解 和 之間的區別RuntimeException


2. Throws:檢查異常

拋出:檢查異常

所有 Java 異常都分為兩類:已檢查未檢查

所有繼承RuntimeExceptionor的異常Error都被認為是未經檢查的異常 所有其他都是已檢查的異常

重要的!

在引入檢查異常 20 年後,幾乎每個 Java 程序員都認為這是一個錯誤。在流行的現代框架中,95% 的異常都是未經檢查的。幾乎一模一樣照抄Java的C#語言,並沒有加入checked exceptions

已檢查異常未檢查異常之間的主要區別是什麼?

對已檢查的異常有額外的要求。粗略地說,它們是這些:

要求 1

如果一個方法拋出一個已檢查的異常它必須在它的簽名中指明異常的類型這樣,調用它的每個方法都知道這個“有意義的異常”可能會發生在其中。

在關鍵字後的方法參數後面指明檢查異常(不要誤用關鍵字)。它看起來像這樣:throwsthrow

type method (parameters) throws exception

例子:

檢查異常 未經檢查的異常
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

在右邊的例子中,我們的代碼拋出了一個未經檢查的異常——不需要額外的操作。 在左側的示例中,該方法拋出一個已檢查的異常,因此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子句中。

就好像我們在說,“這些異常非常重要,我們必須捕獲它們。如果我們不知道如何處理它們,那麼必須通知任何可能調用我們方法的人,其中可能會發生此類異常。

例子:

想像一下,我們正在編寫一種方法來創建一個由人類居住的世界。初始人數作為參數傳遞。所以如果人數太少,我們需要添加例外。

創造地球 筆記
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
該方法可能會拋出兩個已檢查的異常:

  • 空世界異常
  • 孤獨世界異常

此方法調用可以通過 3 種方式處理:

1.不要捕獲任何異常

當方法不知道如何正確處理這種情況時,通常會這樣做。

代碼 筆記
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
調用方法不捕獲異常並且必須將它們通知其他人:它將它們添加到自己的throws子句中

2.捕捉一些異常

我們處理我們可以處理的錯誤。但是我們不理解的,我們把它們丟給調用方法。為此,我們需要將他們的名字添加到 throws 子句中:

代碼 筆記
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
調用者只捕獲一個已檢查的異常—— LonelyWorldException. 另一個異常必須添加到它的簽名中,在throws關鍵字之後表明它

3.捕獲所有異常

如果該方法沒有向調用方法拋出異常,那麼調用方法總是確信一切正常。並且它將無法採取任何行動來修復異常情況。

代碼 筆記
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
所有異常都在此方法中捕獲。來電者將確信一切順利。


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.

如果需要,您可以從那裡輕鬆檢索它。例子:

代碼 筆記
try
{
   // Code where we wrap the checked exception
   // in a RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // Exception handling code goes here
   }
}







獲取存儲在對象內部的異常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| RuntimeExceptione),因為RuntimeException類繼承了Exception



5.自定義異常

您始終可以創建自己的異常類。您只需創建一個繼承該類的類RuntimeException。它看起來像這樣:

class ClassName extends RuntimeException
{
}

我們將在您學習 OOP、繼承、構造函數和方法覆蓋時討論細節。

然而,即使你只有一個像這樣的簡單類(完全沒有代碼),你仍然可以基於它拋出異常:

代碼 筆記
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




拋出一個未經檢查的 MyException.

Java 多線程任務中,我們將深入研究如何處理我們自己的自定義異常。