Java編程思想學習錄(連載之:異常)

Samsung Display


Thinking in java系列博文目錄:java

本篇文章將講述關於異常的相關知識程序員

注: 本文首發於 My 公衆號 CodeSheep ,可 長按掃描 下面的 當心心 來訂閱 ↓ ↓ ↓編程

CodeSheep · 程序羊


基本概念

  • Java使用異常來提供一致性的錯誤報告模型;且可集中錯誤處理;且任務代碼與異常代碼分割開來,易於理解和維護
  • 雖然異常處理理論有終止模型恢復模型兩種,但恢復模型很難優雅地作到,∴並不實用,實際中你們都是轉向使用終止模型代碼
  • 一個異常拋出後發生的兩件事:① 使用new在堆上建立異常對象;② 異常處理機制開始接管流程(當前的執行流程被終止)
  • 標準異常類均有兩個ctor:① default ctor; ② 帶字符串參數的ctor
  • Throwable是異常類型的根類
  • catch異常時,try中拋出的是子類異常,但catch的是基類異常也是OK,但若catch子類異常和基類異常的子句同時存在時,應將基類catch子句放在後面避免「屏蔽」現象發生

拋出異常 + 捕獲異常

  • 拋出異常(throw):
if( t==null )
  throw new NullPointerException(); // 異常對象用new建立於堆上
  • 捕獲異常(try+catch):
try {
  ...
} catch( Type1 id1 ) { 
  // 處理Type1類型的異常代碼
} catch( Type2 id2 ) {
  // 處理Type2類型的異常代碼
}
  1. 雖然上面的id1和id2在處理異常代碼中可能用不到,但不能少,必須定義
  2. 異常發生時,異常機制搜尋參數與異常類型相匹配的第一個catch子句並進入

建立自定義異常

建立不帶參數ctor的自定義異常類:安全

// 自定義異常類(default ctor)
class SimpleException extends Exception {}
------------------------------------------------------------

// 客戶端代碼
public class UseException {
  public void fun throws SimpleException {
    System.out.println( "Throw SimpleExcetion from fun" );
    throw new SimpleException();
  }

  public static void main( String[] args ) {
    UseException user = new UseException();
    try {
      user.fun(); 
    } catch( SimpleException e ) {
      System.out.println("Caught it !");
    }
  }
}
------------------------------------------------------------
// 輸出
Throw SimpleExcetion from fun
Caught it !

建立帶參數ctor的自定義異常類網絡

// 自定義異常類(有參ctor)
class MyException extends  Exception {
  public MyException() { }
  public MyException( String msg ) { super(msg); }
}
------------------------------------------------------------

// 客戶端代碼
public class UseException {
  
  pubilc static void f() throws MyException {
    System.out.println( "Throwing MyException from f()" )
    throw new MyException();
  }
  public static void g() throws MyException {
    System.out.println( "Throwing MyException from g()" )
    throw new MyException("Originated in g()");
  }

  publib static void main( String[] args ) {
    try {
      f();
    } catch( MyException e ) {
      e.printStackTrace( System.out );
    }

    try {
      g();
    } catch( MyException e ) {
      e.printStackTrace( System.out );
    }
  }

}
------------------------------------------------------------

// 輸出
Throwing MyException from f()
MyException
      at ...
      at ...
Throwing MyException from g()
MyException: Originated in g() // 此即建立異常類型時傳入的String參數
      at ...
      at ...

捕獲全部異常

try {
  ...
} catch( Exception e ) { // 填寫異常的基類,該catch子句通常置於末尾
  ...
}

Exception類型所持有的方法:函數

  • String getMessage()
  • String getLocalizedMessage()

  • String toString()

  • void printStackTrace()
  • void printStackTrace( PrintStream )
  • void printStackTrace( javo.io.PrintWriter )

注意:從下往上每一個方法都比前一個提供了更多的異常信息!學習


棧軌跡

printStackTrace()方法所提供的棧軌跡信息能夠經過getStackTrace()方法來Get,舉例:code

try {
  throw new Exception();
} catch( Exception e ) {
  for( StackTraceElement ste : e.getStackTrace() )
    System.out.println( ste.getMethodName() );
}

這裏使用getMethodName()方法來給出異常棧軌跡所通過的方法名!orm


重拋異常

try {
  ...
} catch( Exception e ) {
  throw e;   // 從新拋出一個異常!
}

若只是簡單地將異常從新拋出,則然後用printStackTrace()顯示的將是原異常拋出點的調用棧信息,而非從新拋出點的信息,欲更正該信息,可使用fillInStackTrace()方法:對象

try {
  ...
} catch( Exception e ) {
  throw (Exception)e.fillInStackTrace(); // 該行就成了異常的新發生地!
}

異常鏈

異常鏈:在捕獲一個異常後拋出另外一個異常,並但願將原始的異常信息保存下來!

解決辦法:

  1. 在異常的ctor中加入cause參數
  2. 使用initCause()方法

注意:Throwable子類中,僅三種基本的異常類提供了待cause參數的ctor(Error、Exception、RuntimeException),其他狀況只能靠initCause()方法,舉例:

class DynamicFieldsException extends Exception { }

public Object setField( String id, Object value ) throws DynamicFieldsException {

  if( value == null ) {
    DynamicFieldsException dfe = new DynamicFieldsException();
    dfe.initCause( new NullPointerException() ); 
    throw dfe;
  }

  Object result = null;
  try {
    result = getField(id);
  } catch( NoSuchFieldException e ) {
    throw new RuntimeException( e );
  }

}

Java標準異常

Java標準異常類體系

  • 看這個圖須要明確:程序員通常關心Exception基類型的異常
  • 由圖中可知,Error、RuntimeException都叫作「Unchecked Exception」,即不檢查異常,程序員也無需寫異常處理的代碼,這種自動捕獲
  • 若諸如RuntimeException這種Unchecked異常沒有被捕獲而直達main(),則程序在退出前將自動調用異常的printStackTrace()方法

使用finally進行清理

try {
  ...
} catch(...) {
  ...
} finally { // finally子句老是會被執行!!!
  ...
}

使用時機:

  • 當須要把內存以外的資源(如:文件句柄、網絡鏈接、某個外部世界的開關)恢復到初始狀態時!
try {
  ...
} catch(...) {
  ...
} finally { // finally子句老是會被執行!!!
  sw.off(); // 最後老是須要關掉某個開關!
}
  • 在return中使用finally
public static void func( int i ) {
  
  try {
    if( i==1 )
      return;
    if( i==2 )
      return;
  } finally {
    print( "Performing cleanup!" ); // 即便上面有不少return,但該句確定被執行
  }

}

finally存在的缺憾:兩種狀況下的finally使用會致使異常丟失!

  • 前一個異常還未處理就拋出下一個異常
// 異常類
class VeryImportantException extends Exception {
  poublic String toString() {
    return "A verfy important exception!";
  }
}

class HoHumException extends Exception {
  public String toString() {
    return "A trivial exception!";
  }
}
------------------------------------------------------------------
// 使用異常的客戶端
public class LostMessage {
  void f() throws VeryImportantException {
    throw new VeryImportantException();
  }

  void dispose() throws HoHumException {
    throw new HoHumException();
  }

  public static void main( String[] args ) {
    try {
      LostMessage lm = new LostMessage();
      try {
        lm.f();
      } finally {
        lm.dispose(); // 最後只會該異常生效,lm.f()拋出的異常丟了!
      }
    } catch( Exception e ) {
      System.out.println(e);
    }
  }
}
-----------------------------------------------------------------
// 輸出
A trivial exception!
  • finally子句中的return
public static void main( String[] args ) {
  try {
    throw new RuntimeException();
  } finally {
    return; // 這將會掩蓋全部的異常拋出
  }
}

繼承基類、實現接口時的異常限制

// 異常類
class A extends Exception { }
class A1 extends A { }
class A2 extends A { }
class A1_1 extends A1 { }

class B extends Exception { }
class B1 extends B { }
-------------------------------------------------
// 用了異常類的基類
abstract class Base {
  public Base() throws A { }
  public void event() throws A { }                   // (1)
  public abstract void atBat throws A1, A2;
  public void walk() { }
}
-------------------------------------------------
// 用了異常類的接口
interface Interf {
  public void event() throws B1;
  public void rainHard() throws B1;
}
-------------------------------------------------
// 繼承基類並實現接口的客戶端類
public class Ext extends Base implements Interf {

  public Ext() throws B1, A { }            // (2)
  public Ext( String s ) throws A1, A {}   // (2)
  public void walk() throws A1_1 { }       // (3) 編譯錯誤!
  public void rainHard() throws B1 {}      // (4)
  public void event() { }                  // (5)
  public void atBat() throws A1_1 { }      // (6)

  public static void main( String[] args ) {
  
    try {
      Ext ext = new Ext();
      ext.atBat();
    } catch( A1_1 e ) {
      ...
    } catch( B1 e ) {
      ...
    } catch( A e ) {
      ...
    }

    try {
      Base base = new Ext();
      ext.atBat();
    } catch( A2 e ) { // 這裏的catch必須按照Base中函數的異常拋出來寫
      ...
    } catch( A1 e ) {
      ...
    } catch( B1 e ) {
      ...
    } catch( A ) {
      ...
    }
    
  }
}

上面的例子能夠總結以下:【注意對應數字標號】

  • (1) 基類的構造器或者方法聲明瞭拋出異常,但實際上沒有,這裏至關於爲繼承類寫了一個異常拋出規範,子類實現時安裝這個規範來拋異常
  • (2) 從這兩個ctor看出:異常限制對ctor不生效,子類ctor能夠拋出任何異常而無論基類ctor所拋出的異常
  • (3) 基類函數沒拋異常,派生類重寫時不能瞎拋!
  • (4) 徹底遵照基類的拋出,正常狀況
  • (5) 基類函數拋了異常,派生類重寫時不拋也是OK的
  • (6) 派生類重寫基類函數時拋的異常能夠是基類函數拋出異常的子類型

構造器中異常如何書寫

對於在構造階段可能會拋出異常並要求清理的類,安全的方式是使用嵌套的try子句:即在建立須要清理的對象以後,當即進入一個try-finally塊,舉例:

特別須要注意的是下面的例子裏在ctor中對文件句柄的close應放置的合理位置!

// 須要清理的對象類
class InputFile {
  private BufferedReader in;
  
  InputFile( String fname ) throws Exception {  // 構造函數!
    try {
      in = new BufferedReader( new FileReader(fname) );
      // 這裏放置可能拋出異常的其餘代碼
    } catch( FileNotFoundException e ) { // 若上面的FileReader異常,將會拋FileNotFoundException,走到這裏,該分支無需in.close()的
      System.out.println( "Could not open " + fname );
      throw e;
    } catch( Exception e ) {
      // 走到這裏其實說明in對象已經構建成功,這裏是必須in.close()的
      try {
        in.close();   // 注意此處關閉動做單獨用try進行保障
      } catch( IOException e2 ) {
        System.out.println("in.close() unsuccessful");
      }
      throw e;
    } finally {
      // 注意in.close() 不要在此處關閉,由於try中假如BufferedReader構造失敗,此時in對象未生成成功,是無需close()一說的!
    }
  }

  String getLine() {
    String s;
    try {
      s = in.readLine();
    } catch( IOException e ) {
      System.out.println( "readLine() unsuccessful!" );
      s = "failed";
    }
    return s;
  }

  void cleanup() {  // 提供手動的關閉文件句柄的操做函數
    try {
      in.close();
    } catch( IOException e ) {
      System.out.println( "in.close() failed !" );
    }
  }

}
----------------------------------------------------
// 客戶端代碼
public class Cleanup {

  public static void main( String[] args ) {
    
    try {
      InputFile in = new InputFile( "Cleanup.java" );
      try { // 上面InputFile構造完成之後當即進入該try-finally子句!
        String s = "";
        int i = 1;
        while( (s = in.getLine()) != null )
          System.out.println(""+ i++ + ": " + s);
      } catch( Exception e ) {
        e.printStackTrace( System.out );
      } finally {  // 該finally必定確保in能正常cleanup()!
        in.cleanup();
      } 
    } catch( Exception e ) {
      System.out.println( "InputFile ctor failed!" );
    }

  } // end main()
}
相關文章
相關標籤/搜索