(Thinking in Java)第12章 經過異常處理錯誤

1、概念

使用異常能下降處理錯誤代碼的複雜程度,而且將錯誤在一個地方進行處理,因而將「描述在正常行爲過程當中作過什麼事」的代碼和「出了問題怎麼辦」的代碼相分離java

2、基本異常

異常情形指的是當前環境下沒有足夠的信息來讓咱們解決這個問題,好比當除數爲0發生的時候,咱們不知道除數爲零表明着什麼,(好比在算淘寶購物花銷所佔百分比的時候,你發現你這個月根本沒花錢,總數是零),並不知道該如何處理這個異常。所以就要拋出異常
拋出異常後,會在堆上new出一個新的異常對象,而後當前執行路徑終止,並彈出這個異常對象的引用,而後異常處理機制接管程序抓住這個異常,進行異常處理。
拋出異常的時候就像這樣。編程

throw new NullPointerException()

1.異常參數

異常也是對象,也有他本身的構造器,當在堆上new出一個異常對象的時候,他也能夠執行不一樣的對象構造器。標準異常類都有兩個構造器:一個是默認構造器;另外一個是接受字符串參數,好比:api

throw new NullPointerException("t=null");

從效果上看,將這個異常給throw了,就像是從方法中「返回」同樣,另外還能用拋出異常的方式從當前做用域退出。
可以拋出任意類型的Throwable對象,他是異常類型的根類。安全

3、捕獲異常

首先要理解監控區域的概念,他是一段可能產生異常的代碼,後面跟着處理這些可能出現的異常的代碼。數據結構

1.try塊

若是在方法內部拋出了異常,那麼這個方法將在拋出異常的時候結束,若是不但願方法直接結束,能夠在方法內設置一個塊來「嘗試」各類可能產生異常的方法。app

try{
//code
}

2.異常處理程序

拋出的異常必須在異常處理程序中獲得處理。異常處理程序跟隨在try塊後ide

try{
}catch(Type1 id1){
//handle exceptions of type1
}catch(Type2 id2){
}

當在try塊中出現異常後,異常被拋出,異常處理程序將負責搜尋與這個異常參數類型匹配的第一個異常處理程序,而後進行異常處理,一旦catch結束,則異常處理程序的查找過程結束。函數

3.終止與恢復

異常處理有兩種模型,Java支持終止模型,一旦異常被拋出,代表錯誤沒法挽回,沒法退回來繼續執行以前出錯的代碼。
另外一種叫作恢復模型,指的是異常處理程序的工做是修正錯誤而後從新嘗試調用出問題的方法,並認爲第二次能成功。
clipboard.pngui

4、建立自定義異常

要本身定義異常類,必須從已有的異常類繼承,最好選擇意思相近的異常類繼承。this

package tij.exception;

public class Test {
    void f() throws SimpleException {
        System.out.println("Throw SimpleException from f()");
        throw new SimpleException();
    }
    public static void main(String[] args) {
        Test t = new Test();
        try {
            t.f();
        } catch (SimpleException e) {
            System.out.println("Caught it");
        }
    }
}
class SimpleException extends Exception {}

對於異常來講,最重要的部分就是類名。
這個例子的結果被打印到了控制檯上,也能夠經過寫入System.err將錯誤發送給標準錯誤流。一般這比把錯誤輸出到System.out要好,由於System.out也許會被重定向。

package tij.exception;

public class Test {
    static void f() throws MyException {
        System.out.println("Throw MyException from f()");
        throw new MyException();
    }
    static void g() throws MyException {
        System.out.println("Throw MyException from g()");
        throw new MyException();
    }
    public static void main(String[] args) {
        try {
            f();
        } catch (MyException e) {
            e.printStackTrace(System.out);
        }
        try {
            g();
        } catch (MyException e) {
            e.printStackTrace();
        }

    }
}

class MyException extends Exception {
    public MyException() {}
    public MyException(String msg) {
        super(msg);
    }
}

在異常處理程序中,調用了在Throwable類(Exception也是從他繼承的)的printStackTrace方法,它將打印「從方法調用處知道異常拋出處的方法調用序列」,在上例中,若是信息被髮送到了System.out,則將信息顯示在輸出中,若是使用默認版本e.printStackTrace則將輸出到標準錯誤流。
clipboard.png

看,上下兩個顏色不同

1.異常與記錄日誌

可使用java.util.logging將輸出記錄到日誌中。

package tij.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

public class Test {
    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);
        }
    }
}

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

}

LoggingException首先建立了一個Logger對象,這個對象會將其輸出發送到System.err。若是爲了產生日誌記錄信息,如今咱們想把棧軌跡記錄下來。而printStackTrace不會產生字符串,所以採用了帶有PrintWriter參數的printStackTrace方法,這個方法會將棧軌跡的字符串信息傳入到PrintWriter中,而後將棧軌跡信息穿進trace,而後運用severe方法向Logger寫入信息。(其實書上沒說這個是我猜的)
StringWriter我之前也沒有見過,因而查了查api,用了一下發現挺好玩

一個字符流,能夠用其回收在字符串緩衝區中的輸出來構造字符串。

而後試了試這玩意有啥用

public class Test {
    public static void main(String[] args) {
        StringWriter str=new StringWriter();
        PrintWriter pw=new PrintWriter(str);
        pw.print("abc");
        System.out.println(str.toString());
    }
}

看起來這個StringWriter就是用來收集各類緩衝區裏的字符串的。上面的代碼也就好解釋了。
好回到原來的問題,更常見的情形是,咱們須要捕捉與記錄其餘人編寫的異常,所以能夠在異常處理程序中生成日誌信息

package tij.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

public class Test {
    private static Logger logger = Logger.getLogger("LoggingException");
    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);
        }
    }
}

還能夠進一步自定義異常,好比加入額外的構造器和成員

package tij.exception;

public class Test {
    static void f() throws MyException{
        System.out.println("Throwing MyException from f()");
        throw new MyException();
    }
    static void g() throws MyException{
        System.out.println("Throwing MyException from g()");
        throw new MyException("Originated in g()",47);
    }
    
    public static void main(String[] args) {
        try{
            f();
        }catch(MyException e){
            e.printStackTrace();
        }
        try{
            g();
        }catch(MyException e){
            e.printStackTrace();
        }
    }
}

class MyException extends Exception{
    private int x;
    public MyException(){}
    public MyException(String msg){
        super(msg);
    }
    public MyException(String msg,int x){
        super(msg);
        this.x=x;
    }
    public int val(){
        return x;
    }
    public String getMessage(){
        return "Detail Message: "+x+" "+super.getMessage();
    }
    
}

5、異常說明

大段文字,沒啥可說
clipboard.png

6、捕獲全部異常

一個簡單的

catch(Exception e)

能夠捕獲全部類型的異常,由於Exception是全部與編程相關的異常的父類,但最好把他放在處理列表的末尾。
Exception做爲父類天然不會有太多具體信息,但他能夠調用從Throwable繼承下來的方法好比

  • String getMessaget()
    獲取異常詳細信息
  • String getLocalizedMessage
    用本地語言表示的異常信息
  • void printStackTrace()
    打印調用棧軌跡,調用棧顯示了「發你帶到異常拋出地點」的方法調用序列,這個方法輸出到標準錯誤流。
  • void printStackTrace(PrintWriter)
    能夠選擇輸出的流
  • void printStackTrace(PrintStream)
    能夠選擇輸出的流
  • void fillinStackTrace(PrintStream)
    用於在Throwable對象的內部記錄棧幀的當前狀態

1.棧軌跡

clipboard.png

package tij.exception;

import java.util.Arrays;

public class Test {
    static void f() throws Exception{
        System.out.println("Throwing Exception from f()");
        throw new Exception();
    }
    
    public static void main(String[] args) {
        try{
            f();
        }catch(Exception e){
            System.out.println(Arrays.asList(e.getStackTrace()));
            e.printStackTrace();
        }
    }
}

看起來棧中的一幀指的是一次方法調用啊

2.從新拋出異常

當前異常處理程序裏也能夠從新拋出異常

catch(MyException e){
            throw e;
        }

若是要想把當前的異常對象從新拋出,那再調用printStackTrace方法的時候將是原來異常拋出點的調用棧信息,沒有從新拋出點的信息,要想更新這個信息,能夠調用fillInStackTrace方法,這將返回一個Throwable對象,它是經過把當前調用棧信息填入原來那個異常對象而創建的。

package tij.exception;

public class Test {
    static void f() throws Exception {
        System.out.println("originating the exception from f()");
        throw new Exception();
    }
    static void g() throws Exception {
        try {
            f();
        } catch (Exception e) {
            System.out.println("Inside g().e.printStackTrace");
            e.printStackTrace(System.out);
            throw e;
        }
    }
    
    static void h() throws Exception{
        try{
            f();
        }catch(Exception e){
            System.out.println("Inside h().e.printStackTrace");
            e.printStackTrace(System.out);
            throw (Exception)e.fillInStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (Exception e) {
            System.out.println("main:printStackTrace()");
            e.printStackTrace(System.out);
        }
        try {
            h();
        } catch (Exception e) {
            System.out.println("main:printStackTrace()");
            e.printStackTrace(System.out);
        }
        
    }
}

對比輸出結果,發如今主程序中的針對h的catch塊中打印棧軌跡的時候,發現她只有兩行,由於他捕捉到的異常實際上是(Exception)e.fillInStackTrace(),這實際上是一個新返回的異常,它只記錄了本身這個位置的棧信息,由於他是一個新的異常。
恩要注意從新拋出的異常和原來的異常究竟是啥關係,極可能就沒啥關係的

3.異常鏈

在捕獲一個異常後拋出另外一個異常,並但願吧原是一場的信息保留下來,這被稱爲異常鏈。全部Throwable的子類的構造器能夠接受一個cause對象做爲參數,cause表示原始異常對象。

clipboard.png

package tij.exception;

public class Test {

    public static void main(String[] args) {
        DynamicFields df = new DynamicFields(3);
        System.out.println(df);
        try {
            df.setField("d", "a value of d");
            df.setField("killer47", 47);
            df.setField("fatkiller48", 48);
            System.out.println(df);
            df.setField("d", "a new value of d");
            df.setField("thinkiller", 11);
            System.out.println("df:" + df);
            System.out.println("df.getField(\"d\")" + df.getField("d"));
            Object field = df.setField("d", null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (DynamicFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class DynamicFieldException extends Exception {}
class DynamicFields {
    private Object[][] fields;
    public DynamicFields(int initialSize) {
        this.fields = new Object[initialSize][2];
        for (int i = 0; i < initialSize; i++) {
            fields[i] = new Object[]{null, null};
        }
    }

    public String toString() {
        StringBuilder result = new StringBuilder();
        for (Object[] object : fields) {
            result.append(object[0] + ": " + object[1] + "\n");
        }
        return result.toString();
    }

    private int hasField(String id) {
        for (int i = 0; i < fields.length; i++) {
            if (id.equals(fields[i][0]))
                return i;
        }
        return -1;
    }

    private int getFieldNumber(String id) throws NoSuchFieldException {
        int fieldNum = hasField(id);
        if (fieldNum == -1) {
            throw new NoSuchFieldException();
        }
        return fieldNum;
    }

    private int makeField(String id) {
        for (int i = 0; i < fields.length; i++) {
            if (fields[i][0] == null) {
                fields[i][0] = id;
                return i;
            }
        }
        // 若是空間滿了,那就在造一個空間
        Object[][] temp = new Object[fields.length + 1][2];
        for (int i = 0; i < fields.length; i++) {
            temp[i] = fields[i];
        }
        temp[fields.length] = new Object[]{null, null};
        fields = temp;
        return makeField(id);
    }

    public Object getField(String id) throws NoSuchFieldException {
        return fields[getFieldNumber(id)][1];
    }

    public Object setField(String id, Object value)
            throws DynamicFieldException {
        if (value == null) {
            DynamicFieldException dfe = new DynamicFieldException();
            dfe.initCause(new NullPointerException());
            throw dfe;
        }
        int fieldNumber = hasField(id);
        if (fieldNumber == -1) {
            fieldNumber = makeField(id);
        }
        Object result = null;
        try {
            result = getField(id);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        fields[fieldNumber][1] = value;
        return result;
    }
}

其實他就是完成了一個相似於map的數據結構,在

Object field = df.setField("d", null);

這段代碼中,嘗試插入一個value爲null的對兒,他拋出了一個DynamicFieldException異常,這個異常是因爲NullPointerException引發的,在結果中能夠看到,雖然拋出的是DynamicFieldException,但NullPointerException也被記錄了下來

7、Java標準異常

Throwable對象能夠分爲兩類:Error用來表示編譯時和系統錯誤;Exception是能夠被拋出的基本類型。

1.特例:RuntimeException

運行時異常發生的時候會自動被虛擬機拋出不必定要在異常說明中將它們列出來。
但若是不人工捕獲這種異常,他會穿越全部執行路徑直達main方法。

package tij.exception;

public class Test {

    static void f() {
        throw new RuntimeException("From f()");
    }
    static void g() {
        f();
    }
    public static void main(String[] args) {
        g();
    }
}

對於這種異常咱們程序猿內心要有點B數,不處理的話出錯了全崩了

8、使用finally進行清理

對於一些代碼,不管try塊中是否有異常拋出,他們都應該執行。這一般適用於內存回收以外的狀況。能夠運用finally語句。

package tij.exception;

public class Test {
    static int count = 0;
    public static void main(String[] args) {
        while (true) {
            try {
                if (count++ == 0)
                    throw new ThreeException();
                System.out.println("No Exception");
            } catch (ThreeException e) {
                System.out.println("ThreeException");
            } finally {
                System.out.println("in finally clause");
                if (count == 2)
                    break;
            }
        }
    }
}
class ThreeException extends Exception {}

1.finally用來作什麼

clipboard.png
clipboard.png
(額= =Java內存回收機制和構析函數不是一個東西麼?)

package tij.exception;

public class Test {
    private static Switch sw = new Switch();
    static void f() throws OnOffException1, OnOffException2 {}
    public static void main(String[] args) {
        try {
            sw.on();
            f();
        } catch (OnOffException1 e) {
            System.out.println("OnOffException1");
        } catch (OnOffException2 e) {
            System.out.println("OnOffException2");
        } finally {
            sw.off();
        }
    }
}
class OnOffException1 extends Exception {}
class OnOffException2 extends Exception {}

class Switch {
    private boolean state = false;
    boolean read() {
        return this.state;
    }
    void on() {
        this.state = true;
        System.out.println(this);
    }
    void off() {
        this.state = false;
        System.out.println(this);
    }
    public String toString() {
        return state ? "on" : "off";
    }
}

能夠保證sw最後都是關閉的。

2.在return中使用finally

finally總會執行,因此一個方法中,能夠從多個點返回

package tij.exception;

public class Test {
    static void f(int i) {
        try {
            System.out.println("Point 1");
            if (i == 1)
                return;
            System.out.println("Point 2");
            if (i == 2)
                return;
            System.out.println("Point 3");
            if (i == 3)
                return;
        } finally {
            System.out.println("Performing cleanup");
        }
    }
    public static void main(String[] args) {
        for (int i = 1; i <= 4; i++) {
            f(i);
        }
    }
}

3.異常缺失

異常有時候會被輕易地忽略。

package tij.exception;

public class Test {
    void f() throws VeryImportantException {
        throw new VeryImportantException();
    }
    void dispose() throws HoHumException {
        throw new HoHumException();
    }
    public static void main(String[] args) {
        try {
            Test t = new Test();
            try {
                t.f();
            } finally {
                t.dispose();
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

class VeryImportantException extends Exception {
    public String toString() {
        return "A very important exception!";
    }
}
class HoHumException extends Exception {
    public String toString() {
        return "A trival exception";
    }
}

clipboard.png
還有一種更加容易丟失的異常

package tij.exception;

public class Test {
    @SuppressWarnings("finally")
    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        } finally {
            return;
        }
    }
}

9、異常的限制

package tij.exception;

public class Test {
    public static void main(String[] args) {
        try {
            StormyInning si = new StormyInning();
            si.atBat();
        } catch (PopFoul e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (RainedOut e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (BaseballException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        try {
            Inning i = new StormyInning();
            i.atBat();
        } catch (RainedOut e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Strike e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Foul e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BaseballException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}

abstract class Inning {
    public Inning() throws BaseballException {}
    public void event() throws BaseballException {}
    public abstract void atBat() throws Strike, Foul;
    public void walk() {}
}

class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}

interface Storm {
    public void event() throws RainedOut;
    public void rainHard() throws RainedOut;
}

class StormyInning extends Inning implements Storm {
    // 對於構造方法來講,你能夠添加新的拋出異常,可是你必須也得拋出父類構造方法所聲明的異常
    public StormyInning() throws RainedOut, BaseballException {}

    public StormyInning(String s) throws Foul, BaseballException {}

    // 普通方法拋出的異常必須必須遵循父類,父類拋啥你拋啥,拋多了也不行,父類不拋你也不準拋,阿父真的很嚴格
    // public void walk() throws PopFoul{}

    // 能夠看到接口和父類中有一個相同的方法event,他們拋出了不一樣的異常,前面說了繼承方法不能多拋異常,因此即便是接口,也不能向父類中已經存在的方法添加新的拋出異常
    // public void event() throws RainedOut{}

    // 但rainHard只在接口中出現了,一樣也不能多拋其餘異常
    public void rainHard() throws RainedOut {}

    // 但慶幸的是即便父類或者接口的方法拋異常了,子類重寫的方法能夠不拋異常,就是說能夠偷懶恩
    public void event() {}

    // 而且子類拋出的異常能夠遵循繼承原則,下面這個函數中至關於把Strike異常忽略了,而後拋出了Foul異常的子類PopFoul
    public void atBat() throws PopFoul {}
}

看書,書上這段寫的超棒!

10、構造器

class InputFile {
    private BufferedReader in;
    InputFile(String fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
        } catch (FileNotFoundException e) {
            System.out.println("Could not open " + fname);
            // 這個文件並無被成功的打開
            throw e;
        } catch (Exception e) {
            try {
                in.close();
            } catch (IOException e2) {
                System.out.println("in沒有被成功關閉");
            }
            throw e;
        } finally {
            // 不要關閉這個文件
        }
    }

    String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch (IOException e) {
            throw new RuntimeException("readLine() failed");
        }
        return s;
    }

    void dispose() {
        try {
            in.close();
            System.out.println("dispose() successful");
        } catch (IOException e2) {
            throw new RuntimeException("in.close() failed");
        }
    }
}

從中能夠看出,若是in這個對象建立失敗,他會拋出一個建立異常,而且它不須要關閉,由於他根本沒有被成功建立出來;若是in這個對象若是建立成功了,但若是除了其餘的岔子,這個in應該被關閉掉,這個對象的構造函數具有了這個功能。

public class Test {
    public static void main(String[] args) {
        try {
            InputFile in = new InputFile("src\\tij\\exception\\Test.java");
            String s;
            try {
                while ((s = in.getLine()) != null) {

                }
            } catch (Exception e) {
                System.out.println("caught Exception in main");
                e.printStackTrace(System.out);
            } finally {
                in.dispose();
            }
        } catch (Exception e) {
            System.out.println("InputFile construction failed");

        }
    }
}

因爲InputFile in這個對象知足兩個特徵:1.構造的時候可能產生異常。2.用完以後須要被清理。所以上面的try-catch嵌套用法是最安全的。由於它保證了:1.若是建立失敗,直接拋出異常,這個對象不須要也不該該執行關閉方法(所以不能傻了吧唧的都把close丟finally塊中)。2.若是建立成功,那麼應該保證這個對象在用完以後關閉掉。
clipboard.png

package tij.exception;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
        NeedsCleanup nc1=new NeedsCleanup();
        try{
            
        }finally{
            nc1.dispose();
        }
        
        NeedsCleanup nc2=new NeedsCleanup();
        NeedsCleanup nc3=new NeedsCleanup();
        try{
            
        }finally{
            nc3.dispose();
            nc2.dispose();
        }
        
        try {
            NeedsCleanup2 nc4=new NeedsCleanup2();
            try {
                NeedsCleanup2 nc5=new NeedsCleanup2();
                try{
                    
                }finally{
                    nc5.dispose();
                }
            } catch (ConstructionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }finally{
                nc4.dispose();
            }
        } catch (ConstructionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
            
        
    }
}

class NeedsCleanup{
    //個人構造不會出錯
    private static long counter=1;
    private final long id=counter++;
    public void dispose(){
        System.out.println("NeedsCleanup "+id+" disposed");
    }
}

class ConstructionException extends Exception{}

class NeedsCleanup2 extends NeedsCleanup{
    public NeedsCleanup2() throws ConstructionException{
        
    }
}

nc123都不會出錯,而nc45均可能出錯的,上面的方法雖然麻煩,可是可行且可靠。

11、異常匹配

從上到下,遵循繼承

12、其餘可選方式

書上這段寫的主要是思想方面的事,回來補,今天累= =馬克

end

相關文章
相關標籤/搜索