1. 程式人生 > >Java程式設計思想第四版讀書筆記——第十二章 通過異常處理錯誤

Java程式設計思想第四版讀書筆記——第十二章 通過異常處理錯誤



第十二章 通過異常處理錯誤

Java的基本理念是“結構不佳的程式碼不能執行”。 Java中異常處理的目的在於通過使用少於目前數量的程式碼來簡化大型、可靠的程式的生成,並且通過這種方式可以使程式設計師增加自信。

1、概念

因為異常機制將保證能夠捕獲這個錯誤,所以不用小心翼翼的各種去檢查。而處理錯誤只需要在一個地方完成,那就是 異常處理程式

只需要在異常處理程式中處理錯誤。

2、基本異常

異常情形是指阻止當前方法或作用域繼續執行的問題。
異常處理程式將程式從錯誤狀態中恢復,以使程式要麼換一種方式執行,要麼繼續執行下去。
在沒有其它辦法的情況下,異常允許我們強制程式停止執行,並告訴我們出現了什麼問題。理想狀態下,還可以強制程式處理問題,並返回到穩定狀態的。
異常引數:

用new在堆上建立異常物件,所有標準異常類都有兩個構造器,一個預設的,一個帶參的。
能夠丟擲任意型別的Throwable物件,它是異常型別的根類。

3、捕獲異常

監控區域是一個可能產生異常的程式碼,並且後面跟著處理這些異常的程式碼。

如果在方法內部丟擲了異常,那麼這個方法就此結束。如果不希望這個方法結束,那麼可以在方法內設定一個特殊的塊來捕獲異常,即try塊。為什麼叫try呢,因為在這個塊裡“嘗試”各種可能產生異常的方法進行呼叫,所以是try。

try {
// Code that might generate exceptions
} catch(Type1 id1)|{
// Handle exceptions of Type1

} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
}

異常丟擲後,異常處理機制將搜尋引數與異常型別相匹配的第一個處理程式,進入catch語句處理,此時認為異常的到了處理。catch子句結束,則處理程式不再往下找匹配了。
終止與恢復:
異常處理理論上有兩種基本模型,java支援終止模型。該模型假設錯誤非常關鍵,一旦異常被丟擲,那麼錯誤已經無可挽回,程式不能繼續執行。
另一種模型是恢復模型,就是先修正錯誤,然後重新進入該方法。這個模型假定了修正完之後再進入執行一定會成功。
相比較終止模型

還是比較佔優的,因為恢復模型需要了解異常丟擲的地點,麻煩。


4、建立自定義異常

可以異常類不寫建構函式,使用預設無參建構函式,也可以寫建構函式。醬紫可以實現在丟擲的異常後面打印出異常所在函式等功能。比如: class MyException extends Exception { 
       public MyException() {} 
       public MyException(String msg) { super(msg); } 
     } 
在丟擲異常時  public static void g() throws MyException { 
          System.out.println("Throwing MyException from g()"); 
          throw new MyException("Originated in g()"); 
       } 
那麼,在列印的時候,就可以打印出 MyException: Originated in g() 
在異常處理程式中,呼叫Throwable類的printStackTrace()方法,那麼“從異常方法呼叫處直到異常丟擲處”的方法呼叫序列將被打印出來。如下: MyException 
               at FullConstructors.f(FullConstructors.java:11) 
               at FullConstructors.main(FullConstructors.java:19)
 
printStackTrace()方法可以帶引數,比如printStackTrace(System.out),這樣打印出來的資訊將被髮送到System.out,如果該方法不帶參,那麼資訊將被輸出到標準錯誤流。
異常與記錄日誌: 使用java.util.logging工具將輸出記錄到日誌中。方法如下: import java.util.logging.*; 
     import java.io.*; 


     class LoggingException extends Exception { 
        private static Logger logger = 
          Logger.getLogger("LoggingException"); 
        public LoggingException() { 
          StringWriter trace = new StringWriter(); 
          printStackTrace(new PrintWriter(trace)); 
          logger.severe(trace.toString()); 
        } 
     } 


     public class LoggingExceptions { 
        public static void main(String[] args) { 
          try { 
             throw new LoggingException(); 
          } catch(LoggingException e) { 
             System.err.println("Caught " + e); 
          } 
          try { 
             throw new LoggingException(); 
} catch(LoggingException e) { 
             System.err.println("Caught " + e); 
          } 
       } 
     } /* Output: (85% match) 
     Aug 30, 2005 4:02:31 PM LoggingException <init> 
     SEVERE: LoggingException 
               at LoggingExceptions.main(LoggingExceptions.java:19) 


     Caught LoggingException 
     Aug 30, 2005 4:02:31 PM LoggingException <init> 
     SEVERE: LoggingException 
               at LoggingExceptions.main(LoggingExceptions.java:24) 


     Caught LoggingException 
當然,不能指望每個程式設計師把記錄日誌的程式的基礎設施都構建在異常裡,所以更常見的情形是需要捕獲和記錄他人編寫的異常,因此需要在異常處理程式中生成日誌訊息,如下: import java.util.logging.*; 
     import java.io.*; 


     public class LoggingExceptions2 { 
       private static Logger logger = 
          Logger.getLogger("LoggingExceptions2"); 
       static void logException(Exception e) { 
          StringWriter trace = new StringWriter(); 
          e.printStackTrace(new PrintWriter(trace)); 
          logger.severe(trace.toString()); 
       } 
       public static void main(String[] args) { 
          try { 
             throw new NullPointerException(); 
          } catch(NullPointerException e) { 
             logException(e); 
          } 
       } 
     } /* Output: (90% match) 
     Aug 30, 2005 4:07:54 PM LoggingExceptions2 logException 
     SEVERE: java.lang.NullPointerException 
               at LoggingExceptions2.main(LoggingExceptions2.java:16) 
我們甚至可以進一步定義異常,比如加入額外的構造器和成員,然而一般來說並不會用上這些功能。

5、異常說明

異常說明使用了關鍵字 throws,後面接一個潛在的異常型別列表。 void f() throws TooBig, TooSmall, DivZero { //... 這種在編譯時被強制檢查的異常稱為被檢查的異常 也可以宣告方法將丟擲異常,但是實際上卻不丟擲。這樣做可以先為異常佔個位置,以後可以丟擲這類異常而不用修改已有方法,這種“作弊”方法通常用在你定義抽象基類和介面時,這樣派生類或者介面實現就能丟擲這些預先宣告的異常。

6、捕獲所有異常

捕獲異常型別的基類Exception(還有其它基類),這可以保證異常一定會被捕獲,最好把它放到異常處理程式列表的末尾。 catch(Exception e) {
System.out.println("Caught an exception");
}
Exception可以呼叫其從基類繼承的方法:
String getMessage( )
String getLocalizedMessage( )

獲取詳細資訊(丟擲異常物件所帶的引數),或者用本地語言表示的詳細資訊。

String toString( )

返回對Throwable的簡單描述,要是有詳細資訊的話,也會把它包含在內。
void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
列印Throwable和Throwable的呼叫棧軌跡。

Throwable fillInStackTrace( )

用於在Throwable物件的內部記錄棧幀的當前狀態。
幾個Object類的方法: getClass() 返回一個表示此物件型別的物件 getName() 查詢這個Class物件包含包資訊的名稱 getSimpleName() 只產生類名稱 import static net.mindview.util.Print.*;
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch(Exception e) {
print("Caught Exception");
print("getMessage():" + e.getMessage());
print("getLocalizedMessage():" +
e.getLocalizedMessage());
print("toString():" + e);
print("printStackTrace():");
e.printStackTrace(System.out);
}
}
} /* Output:
Caught Exception
getMessage():My Exception
getLocalizedMessage():My Exception
toString():java.lang.Exception: My Exception
printStackTrace():
java.lang.Exception: My Exception
at ExceptionMethods.main(ExceptionMethods.java:8)


棧軌跡:

printStackTrace()方法所提供的資訊可以通過getStackTrace()方法來直接訪問,該方法返回一個由棧軌跡元素所構成的陣列,每個元素表示棧中的一幀,元素0也是棧頂元素,是最後呼叫的方法(Throwable被建立和丟擲之處),最後一個元素是棧底,是呼叫序列的第一個方法呼叫。 public class WhoCalled {
static void f() {
// Generate an exception to fill in the stack trace
try {
throw new Exception();
} catch (Exception e) {
for(StackTraceElement ste : e.getStackTrace())
System.out.println(ste.getMethodName());
}
}
static void g() { f(); }
static void h() { g(); }
public static void main(String[] args) {
f();
System.out.println("--------------------------------");
g();
System.out.println("--------------------------------");

h();
}
} /* Output:
f
main
--------------------------------
f
g
main
--------------------------------
f
g
h
main


重新丟擲異常

擋在異常處理模組裡繼續丟擲異常,那麼printStackTrace()方法顯示的將是原來異常丟擲點的呼叫棧資訊,而非重新丟擲點的的資訊。 catch(Exception e) {
System.out.println("An exception was thrown");
throw e;
}

此時可以使用fillinStackTrace()方法 catch(Exception e) {
System.out.println("An exception was thrown");
throw (Exception)e.fillInStackTrace();
}
呼叫fillInStackTrace()的這一行就成為異常的新發生地了。 在異常捕獲之後丟擲另一種異常,其效果類似於fillInStackTrace()。

異常鏈

在捕獲一個異常後丟擲另一個異常,並希望把原始異常的資訊儲存下來,這被稱為異常鏈
Throwable的子類可以在構造器中接受一個case物件作為引數。這個case引數表示原始異常,這樣通過把原始異常傳遞給新的異常。
Throwable子類,只有三種基本異常提供了帶case引數的構造器,它們是Error(用於Java虛擬機器報告系統錯誤)、Exception以及RuntimeException。 看不下去了...嘔...TAT

7、Java標準異常

Throwable物件可分為兩種型別(指從Throwable繼承而得到的型別):Error用來表示編譯時和系統錯誤,Exception是可以被丟擲的基本型別,包括Java類庫,使用者方法以及執行時故障都可以丟擲此異常。 Error一般不用自己關心,來講Exception:

特例RuntimeException

比如nullPointerException,空指標異常。 執行時產生的異常,不需要在異常說明中宣告方法將丟擲RuntimeException型別的異常。它們被稱為“不受檢查的異常”。這種異常屬於錯誤,會被自動捕獲,而不用程式設計師自己寫程式碼捕獲。 如果RuntimeException沒有被捕獲而直達main(),那麼在程式退出前將呼叫異常的printStackTrace()方法。

8、使用finally進行清理

在異常處理程式後面加上finamlly子句,可保證無論try塊裡的異常是否丟擲,都能執行。(通常適用於記憶體回收之外的情況) finally執行未必要放在最後,正常的順序執行到它就是它了。 try {
// The guarded region: Dangerous activities
// that might throw A, B, or C
} catch(A a1) {
// Handler for situation A
} catch(B b1) {
// Handler for situation B
} catch(C c1) {
// Handler for situation C
} finally {
// Activities that happen every time
}
甚至當當前異常1還沒捕獲的情況下,又丟擲了個異常2,那麼這個異常2的finally也會先執行,再到捕獲異常1的程式塊中。 import static net.mindview.util.Print.*;
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
print("Entering first try block");
try {
print("Entering second try block");
try {
throw new FourException();
} finally {
print("finally in 2nd try block");
}
} catch(FourException e) {
System.out.println(
"Caught FourException in 1st try block");
} finally {
System.out.println("finally in 1st try block");
}
}
} /* Output:
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block


當涉及break和continue時,finally子句也會得到執行。 如果把finally子句和帶標籤的break以及continue配合使用,在java裡沒必要使用goto語句了。 有return語句時,finally依舊會執行。 異常丟失: 在一個異常還沒得到處理的情況下,應該儘量避免丟擲另一個異常。 1、使用finally可能導致一個異常還沒處理,在接下來的finally字句中又丟擲了一個異常,那麼前一個異常就會丟失,外面的catch塊捕捉到的就是finally丟擲的異常而未察覺到最開始丟擲的異常。 2、一種更簡單的丟失異常的方式是在finally語句中直接return,這就更別說到catch塊匹配異常了。 應該避免以上兩種程式設計錯誤。 1、 class VeryImportantException extends Exception {
public String toString() {
return "A very 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();
}
} catch(Exception e) {
System.out.println(e);
}
}
} /* Output:
A trivial exception


2、
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new RuntimeException();
} finally {
// Using ‘return’ inside the finally block
// will silence any thrown exception.
return;
}
}
}

9、異常的限制

當覆蓋 方法時,只能丟擲在基類方法的異常說明裡列出的那些異常。這個限制意味著,當基類程式碼運用到派生類時,依舊有用。

class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
public Inning() throws BaseballException {}
public void event() throws BaseballException {
// Doesn’t actually have to throw anything
}
public abstract void atBat() throws Strike, Foul;
public void walk() {} // Throws no checked exceptions
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm {
// OK to add new exceptions for constructors, but you
// must deal with the base constructor exceptions:
public StormyInning()
throws RainedOut, BaseballException {} //異常限制對構造器不管用,只需包含基類構造器的異常說明。派生類構造器中不可捕獲基類構造器丟擲的異常
public StormyInning(String s)
throws Foul, BaseballException {}
// Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//! public void event() throws RainedOut {}   //對於繼承的基類和實現的介面中都有的方法,異常說明表要取兩者交集
// If the method doesn’t already exist in the
// base class, the exception is OK:
public void rainHard() throws RainedOut {}
// You can choose to not throw any exceptions,

// even if the base version does:
public void event() {}      //可以不丟擲任何異常
// Overridden methods can throw inherited exceptions:
public void atBat() throws PopFoul {}
public static void main(String[] args) {
try {
StormyInning si = new StormyInning();
si.atBat();
} catch(PopFoul e) {
System.out.println("Pop foul");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
// Strike not thrown in derived version.
try {
// What happens if you upcast?
Inning i = new StormyInning();
i.atBat();
// You must catch the exceptions from the
// base-class version of the method:
} catch(Strike e) {
System.out.println("Strike");
} catch(Foul e) {
System.out.println("Foul");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
}

當處理派生類物件時,編譯器只會強制要求捕獲派生類該方法產生的異常。如果向上轉型為基類,編譯器會要求捕獲基類方法產生的異常。很智慧的。
異常說明本身並不屬於方法型別的範疇中,因此不參與過載的判斷。

基於特定方法的“異常說明的介面”不是變大了而是變小了,小於等於基類異常說明表——這恰好和類介面在繼承時的情形相反。

10、構造器

好惡心不看


11、異常的匹配

異常匹配並不要求與丟擲的異常完全匹配,也可以匹配該異常的基類。 如果故意把基類異常放在前面,導致子類異常的catch子句永遠得不到執行,編譯器會報錯。 class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
public static void main(String[] args) {
// Catch the exact type:
try {
throw new Sneeze();
} catch(Sneeze s) {
System.out.println("Caught Sneeze");
} catch(Annoyance a) {
System.out.println("Caught Annoyance");

}
// Catch the base type:
try {
throw new Sneeze();
} catch(Annoyance a) {
System.out.println("Caught Annoyance");
}
}
} /* Output:
Caught Sneeze
Caught Annoyance

12、其它可選方式

1、將異常傳遞給控制檯,使用FileInputStream進行開啟關閉操作,記錄在一個檔案中。 2、用RuntimeException來包裝“被檢查的異常”。