《Think in Java》閱讀筆記·第三卷
通過異常處理錯誤
異常情形:是指阻止當前方法或作用域繼續執行的問題。
監控區域:一段可能產生異常的程式碼區域,即try區域。
異常處理程式:處理異常的地點,即catch區域。
異常處理的兩種模型
終止模型:一旦丟擲異常,將無法返回產生異常處。
恢復模型:在產生異常時不丟擲異常,而是通過方法或者將try放到迴圈語句中以修補異常。
注意:恢復模型容易增強程式耦合性
自定義異常
import java.util.logging.Logger;
public class MyException {
private static void f() throws CustomException{
System.out .println("f:");
throw new CustomException();
}
private static void g(String msg)throws CustomException{
System.out.println("g:");
throw new CustomException(msg);
}
private static void h(){
int x,y,z;
x=4;
y=0;
try {
z=x/y;
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String... args) {
try {
f();
} catch (CustomException e) {
e.printStackTrace(System.out);
}
try {
g("msg");
} catch (CustomException e) {
e.printStackTrace(System.out );
}
h();
}
}
class CustomException extends Exception{
private Logger logger=Logger.getLogger("CustomException");
public CustomException(){
}
public CustomException(String msg){
super(msg);
}
@Override
public String getMessage() {
//return super.getMessage();
return "This is my custom error info";
}
}
當註釋掉return super.getMessage():
f:
thinkinjava.CustomException: This is my custom error info
at thinkinjava.MyException.f(MyException.java:7)
at thinkinjava.MyException.main(MyException.java:26)
g:
thinkinjava.CustomException: This is my custom error info
at thinkinjava.MyException.g(MyException.java:11)
at thinkinjava.MyException.main(MyException.java:32)
h:
java.lang.ArithmeticException: / by zero
at thinkinjava.MyException.h(MyException.java:19)
at thinkinjava.MyException.main(MyException.java:36)
當註釋掉return “This is my custom error info”:
f:
thinkinjava.CustomException
at thinkinjava.MyException.f(MyException.java:7)
at thinkinjava.MyException.main(MyException.java:26)
g:
thinkinjava.CustomException: msg
at thinkinjava.MyException.g(MyException.java:11)
at thinkinjava.MyException.main(MyException.java:32)
h:
java.lang.ArithmeticException: / by zero
at thinkinjava.MyException.h(MyException.java:19)
at thinkinjava.MyException.main(MyException.java:36)
getMessage類似於toString方法
異常與記錄日誌
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
public class LoggerException {
private static Logger logger=Logger.getLogger("LoggerException");
private static void logprint(Exception e){
StringWriter sw=new StringWriter();
PrintWriter pw=new PrintWriter(sw);
e.printStackTrace(pw);
logger.severe(pw.toString());
}
public static void main(String...args){
try {
throw new NullPointerException();
} catch (NullPointerException e) {
logprint(e);
}
}
}
五月 01, 2018 9:31:36 下午 thinkinjava.LoggerException logprint
嚴重: [email protected]
throw:拋錯誤給上一層處理
public class Thrower {
static void demoproc() {
try {
throw new NullPointerException("demo");
} catch(NullPointerException e) {
System.out.println("Caught inside demoproc.");
throw e;
}
}
public static void main(String args[]) {
try {
demoproc();
} catch(NullPointerException e) {
System.out.println("Recaught: " + e);
}
}
}
Caught inside demoproc.
Recaught: java.lang.NullPointerException: demo
棧軌跡
public class StackTracer {
private static void a(){
try{
throw new Exception();
}catch (Exception e){
for(StackTraceElement stackTraceElement:e.getStackTrace())
System.out.println(stackTraceElement.getMethodName());
}
}
private static void b(){
a();
}
private static void c(){
b();
}
public static void main(String...args){
a();
System.out.println("-------------");
b();
System.out.println("-------------");
c();
}
}
輸出:
a
main
-------------
a
b
main
-------------
a
b
c
main
從棧底開始呼叫,類似於遞迴
若把當前異常重新丟擲,printStackTrace()顯示的仍然是原來異常丟擲點的呼叫棧資訊
private static void d() throws Exception{
throw new Exception("throw new Exception from d");
}
private static void e() throws Exception{
try{
d();
}catch (Exception e){
System.out.println("e try catch");
e.printStackTrace(System.out);
throw e;
}
}
private static void f() throws Exception{
try{
d();
}catch (Exception e){
System.out.println("f try catch");
e.printStackTrace(System.out);
throw (Exception) e.fillInStackTrace();
}
}
public static void main(String...args){
try {
e();
}catch (Exception e){
System.out.println("main try catch from e");
e.printStackTrace(System.out);
}
try {
f();
}catch (Exception e){
System.out.println("main try catch from f");
e.printStackTrace(System.out);
}
}
e try catch
java.lang.Exception: throw new Exception from d
at thinkinjava.StackTracer.d(StackTracer.java:19)
at thinkinjava.StackTracer.e(StackTracer.java:24)
at thinkinjava.StackTracer.main(StackTracer.java:44)
main try catch from e
java.lang.Exception: throw new Exception from d
at thinkinjava.StackTracer.d(StackTracer.java:19)
at thinkinjava.StackTracer.e(StackTracer.java:24)
at thinkinjava.StackTracer.main(StackTracer.java:44)
f try catch
java.lang.Exception: throw new Exception from d
at thinkinjava.StackTracer.d(StackTracer.java:19)
at thinkinjava.StackTracer.f(StackTracer.java:34)
at thinkinjava.StackTracer.main(StackTracer.java:51)
*main try catch from f
java.lang.Exception: throw new Exception from d
at thinkinjava.StackTracer.f(StackTracer.java:38)
at thinkinjava.StackTracer.main(StackTracer.java:51)
如最後一個輸出結果,其已經不再輸出原來的那個丟擲點了d了
若丟擲的是兩個不同的自定義異常,那麼就無法關聯,即第二次丟擲點打印出的棧軌跡不會有第一次的任何資訊,這是如果我們也要列印第一次丟擲點的棧軌跡,則需要用到異常鏈:
public class ExceptionLink {
class MyException2 extends Exception{
public MyException2(Throwable throwable){
super(throwable);
}
public MyException2(){
super();
}
}
class MyException1 extends Exception{
public MyException1(){
super();
}
}
private void a() throws MyException1{
throw new MyException1();
}
private void b() throws MyException2{
try {
a();
}catch (MyException1 exception1){
System.out.println("b try catch");
exception1.printStackTrace(System.out);
throw new MyException2();
//throw new MyException2(exception1);
}
}
public static void main(String...args){
ExceptionLink exceptionLink=new ExceptionLink();
try {
exceptionLink.b();
} catch (MyException2 myException2) {
System.out.println("main try catch");
myException2.printStackTrace(System.out);
}
}
}
輸出:
b try catch
thinkinjava.ExceptionLink$MyException1
at thinkinjava.ExceptionLink.a(ExceptionLink.java:18)
at thinkinjava.ExceptionLink.b(ExceptionLink.java:22)
at thinkinjava.ExceptionLink.main(ExceptionLink.java:32)
main try catch
thinkinjava.ExceptionLink$MyException2
at thinkinjava.ExceptionLink.b(ExceptionLink.java:26)
at thinkinjava.ExceptionLink.main(ExceptionLink.java:32)
如上在第二個丟擲點並沒有丟擲第一個丟擲點所丟擲的資訊,因為這是兩個不同異常類
若將註釋掉的那行取消掉註釋則會輸出:
b try catch
thinkinjava.ExceptionLink$MyException1
at thinkinjava.ExceptionLink.a(ExceptionLink.java:18)
at thinkinjava.ExceptionLink.b(ExceptionLink.java:22)
at thinkinjava.ExceptionLink.main(ExceptionLink.java:32)
main try catch
thinkinjava.ExceptionLink$MyException2: thinkinjava.ExceptionLink$MyException1
at thinkinjava.ExceptionLink.b(ExceptionLink.java:26)
at thinkinjava.ExceptionLink.main(ExceptionLink.java:32)
Caused by: thinkinjava.ExceptionLink$MyException1
at thinkinjava.ExceptionLink.a(ExceptionLink.java:18)
at thinkinjava.ExceptionLink.b(ExceptionLink.java:22)
... 1 more
這便是異常鏈
finally語句在return語句中使用
try{
return;
}finally{
System.out.println("before return")
}
其會在返回前輸出”before return”
異常丟失
造成異常丟失的兩種情況:
try{
throw new Exception1();
}finally{
throw new Exception2();
}
try{
throw new MyException();
}finally{
return;
}
異常的限制
基類中的構造器和方法都聲明瞭丟擲異常,但在方法體內都沒丟擲異常,目的是讓其派生類在重寫該方法時強制捕獲該異常。
當一個基類和一個介面中都定義了相同的一個方法,但聲明瞭丟擲不同的異常。那麼在繼承了該基類和實現了該介面中的類中實現該方法,就不能丟擲在該類和該介面中該方法中宣告的任何異常,派生類可以不丟擲異常。
2中的情況無法限制構造器,因為在例項化派生類時會自動例項化基類構造器,所以派生類需要宣告基類構造器宣告的丟擲的異常。但是派生類構造器無法捕獲基類構造器的異常,因為在派生類構造器中super(…)必須處於最前端。
如下程式碼4,會編譯出錯,因為子類繼承了父類,然後覆蓋了父類的fun方法,但丟擲了父類方法沒有丟擲的異常。倘若編譯期沒有檢查到錯誤,在執行階段如果呼叫basic.fun()不用捕獲異常,但若Basic basic=new Child();basic.fun()就會需要捕獲異常。
如下程式碼5,c.fun()捕獲了MyException2的異常,而基類中的fun方法聲明瞭丟擲了MyException1異常,這是可行的,因為MyException2繼承於MyException1。
//4
public class Child extends Basic{
//compile erro
public void fun() throws MyException{}
}
class Basic{
public void fun(){}
}
//5
public class MyException2 extends MyException1{}
public class Child extends Basic{
public static final void main(String...args){
Child c=new Child();
try{
c.fun();
}catch(MyException2){}
}
}
class Basic{
public void fun() throws MyException1{}
}
異常匹配
catch(MyException)會捕獲MyException以及所有從它派生的異常
將被檢查異常轉換成不檢查異常
import java.io.IOException;
public class TranstException{
public TranstException(){
}
public static final void main(String...args){
try{
throwToOther(0);
}catch(RuntimeException e){}//吞噬異常
System.out.println("end");
}
private static void throwToOther(int i){
try{
switch(i){
case 0:throw new IOException();
case 1:throw new NullPointerException();
case 2:throw new RuntimeException();
}
throw new IOException();
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
end
將每一個異常封裝成一個RuntimeException,然後通過getCause方法取出:
import java.io.IOException;
public class TranstException{
public TranstException(){
}
public static final void main(String...args){
for(int i=0;i<3;i++){
try{
throwToOther(i);
}catch (RuntimeException er){
try{
throw er.getCause();
}catch (IOException e){
System.out.println("IOException");
}catch (NullPointerException e){
System.out.println("NullPointerException");
}catch (RuntimeException e){
System.out.println("RuntimeException");
} catch (Throwable throwable) {
System.out.println("Throwable");
}
}
}
System.out.println("end");
}
private static void throwToOther(int i){
try{
switch(i){
case 0:throw new IOException();
case 1:throw new NullPointerException();
case 2:throw new RuntimeException();
}
throw new IOException();
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
IOException
NullPointerException
RuntimeException
end