Java 筆記《異常》

lighkLife發表於2019-04-03
  1. 為什麼需要異常?
  2. 程式中可能會出現那些錯誤和問題?
  3. 異常有哪些種類?
  4. 常見的異常處理方式有哪些?
  5. 使用異常機制的技巧有哪些?

1. 為什麼需要異常?

使用者在遇到錯誤時會感覺到很不爽,如果一個使用者在執行程式期間,由於程式的一些錯誤或者外部環境的影響造成了使用者資料的丟失,使用者就有可能不在使用這個程式了。為了避免此類事情發生,至少應該做到:

  • 向使用者報告錯誤
  • 儲存所有的工作結果
  • 允許使用者以妥善的形式退出程式
  • 返回到一種安全狀態,並能夠讓使用者執行一些其他的操作

Java提供的異常捕獲機制來改善這種情況。

某個方法不能採用正常的途徑完成它的任務,就可能通過另一個路徑退出該方法。 這種情況下,方法並不返回任何值,而是丟擲(throw)一個封裝了錯誤資訊的物件。這個方法會 立刻退出,並不返回任何值,呼叫這個方法的程式碼也將無法執行,而是異常處理機制開始搜尋能夠處理這種異常狀況的異常處理器(exception handler)。

2.程式中可能會出現那些錯誤和問題?

  • 使用者輸入錯
  • 裝置錯誤
  • 物理限制
  • 程式碼錯img

3.異常有哪些種類?

Java異常層次結構

3.1 Exception

JavaAPI裡是這樣描述的:

  • extends Throwable

The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.

The class Exception and any subclasses that are not also subclasses of RuntimeException are checked exceptions. Checked exceptions need to be declared in a method or constructor's throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary.

3.2 RuntimeException派生出

  1. NullPointerException - 空指標引用異常。
  2. ClassCastException - 型別強制轉換異常。
  3. IllegalArgumentException - 傳遞非法引數異常。
  4. ArithmeticException - 算術運算異常
  5. ArrayStoreException - 向陣列中存放與宣告型別不相容物件異常
  6. IndexOutOfBoundsException - 下標越界異常
  7. NegativeArraySizeException - 建立一個大小為負數的陣列錯誤異常
  8. NumberFormatException - 數字格式異常
  9. SecurityException - 安全異常
  10. UnsupportedOperationException - 不支援的操作異常

JavaAPI裡是這樣描述的:

  • extends Exception

RuntimeException is the superclass of those exceptions that can be thrown during the normal operation of the Java Virtual Machine.

RuntimeException and its subclasses are unchecked exceptions. Unchecked exceptions do not need to be declared in a method or constructor's throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary.

3.3 IOException

  1. 試圖開啟一個不存在的檔案。
  2. 試圖在檔案尾部讀資料。
  3. 試圖根據給的的字串查詢Class物件,而這個字串表示的類並不存在。

Java把所有的ErrorRuntimeException稱為未檢查(unchecked)異常,其他的異常稱為已檢查異常(checked),編譯器會檢查是否未所以的已檢查異常提供了異常處理器。

如果出現了RuntimeException,那麼一定是我的問題

4.常見的異常處理方式有哪些?

4.1 宣告異常

如標準類庫提供的FileInputStream類的一個構造器的宣告:

public FileInputStream(String name) throws FileNotFoundException
複製程式碼

4.2 丟擲異常

String readData(Scanner in) throws EOFException
{
  ...
  while(...)
  {
    if(!in.hasNext()) /*EOF encountered*/
    {
      if(n < len)
        throw new EOFException();
    }
    ...
  }
  return s;
}
複製程式碼

其中為了更加細緻的描述這個異常,EOFException類提供了一個含義字串型別引數的構造器:

String gripe = "Conten - length: " + len + ", Received: " + n;
throw new EOFException(gripe);
複製程式碼

4.3 捕獲異常

try
{
  code
  more code
}
catch(Exception)
{
  handler for this type
}
複製程式碼

有時候需要捕獲多個異常:

try
{
  code that might throw exceptions
}
catch(FileNotFoundException | UnknowHostException)
{
  emergency action for missing file and unkonw hosts
}
catch(IOException)
{
  emergency action for all other I/O problems
}
複製程式碼

注意這裡兩個異常有相同的處理動作時可以合併,合併採用的邏輯或( | ) 而不是短路或(||

4.4 再次丟擲異常與異常鏈

在catch中可以再丟擲一個異常,這樣做的目的是改變異常的型別。比如執行servlet的程式碼可能不想知道發生錯誤的細節原因,但希望明確的知道servlet是否又問題。

try
{
  access the database
}
catch(SQLExeption e)
{
  Throwable se = new ServletException();
  se.initCause(e);
  throw se;
}
複製程式碼

這樣做在捕獲到異常時就可以使用:

Throwable e = se.getCause();
複製程式碼

重新得到原始異常而補丟失細節。

4.5 finally子句

@Test
public void myFinally()
{
    try {
        InputStream in = new FileInputStream("/home/lighk/test.txt");
        try
        {
            in.read();
            /*take care! There is might throw other Exception*/
        }
        finally
        {
            in.close();
        }
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }
}
複製程式碼
  • 內層的try只有一個職責:卻倒關閉輸入流。
  • 外層的try也只有一個職責:確保出現的錯誤。

這裡又一個問題,如果內層的try只是丟擲IOException那麼這種結構很適合。 但是如果內層的try丟擲了非IOException,這些異常只有這個方法的呼叫者才能處理。 執行這個finally語句塊,並呼叫close。而close()方法本身也可能丟擲IOException。 當這種情況出現,原始異常會丟失,轉而丟擲close()方法的異常。然而第一個異常可能會更有意思。 若巢狀多層try語句也可以解決這個問題,但這會讓程式碼變得很繁瑣。 Java 7 未這種程式碼模式提供了一個很又用的快捷方式--帶資源的try語句。

4.6 帶資源的try語句

try(Scanner in = new Scanner(new FileInputStream("/home/lighk/test.txt"));
  PrintWriter out = New PrintWriter("out.txt"))
  {
    while(in.hasNext())
      out.println(in.next().toUpperCase());
  }
複製程式碼

前提是這個資源必須實現了AutoCloseable介面,或者其子介面Closeable.

不論這這個方法如何退出,in & out 都會關閉。若try丟擲一個異常,close()丟擲一個異常,close() 的異常會被`抑制`。這些異常會被自動捕獲,並由addSuppressed方法增加到原來的異常。若對這個異常感興趣, 可以呼叫getSuppressed方法獲取close丟擲並被抑制的異常列表。

5.使用異常機制的技巧有哪些?

  1. 異常不能代替簡單的測試
if(! s.empty())
  s.pop();
複製程式碼
try
{
  s.pop();
}
catch(EmptyStackException e)
{
  /*&emsp;do something */
}
複製程式碼

前者的效能要比後者高的多,因此只在異常情況下使用異常機制。

  1. 不要過分的細化異常,將正常處理與錯誤處理分開
  2. 利用異常的層次結構
  3. 不要壓制異常
  4. 檢測錯誤時,`苛刻`比放任更好 在無效的引數呼叫一個方法時,返回一個虛擬的數字還是丟擲一個異常? eg. when stack is empty, we should return a null or throw a EmptyStackException? Horstmann recommanded :

Throw a EmptyStackException is better than throw a NullPointerException in the following code to the error.

  1. 不要羞於傳遞異常 5和6可以歸結為早丟擲,晚捕獲

相關文章