java 通過異常處理錯誤(上)

java編程思想第12章筆記

1.概念

異常指的是在程序運(yùn)行過程中發(fā)生的異常事件,通常是由硬件問題或者程序設(shè)計問題所導(dǎo)致的。在Java等面向?qū)ο蟮木幊陶Z言中異常屬于對象。
早期的C語言的異常處理機(jī)制,通常是我們?nèi)藶榈膶Ψ祷亟Y(jié)果加一些標(biāo)志來進(jìn)行判定,比如發(fā)生錯誤返回什么標(biāo)志,正常情況下我們又是返回什么標(biāo)記,而這些都不是語言本身所賦予我們的,這種機(jī)制的問題在于,C語言的異常處理機(jī)制全是我們?nèi)藶榈亩x,這樣就會造成業(yè)務(wù)邏輯的主線受到異常處理的牽制,或者說是我們難免會將注意力轉(zhuǎn)移,并且造成業(yè)務(wù)邏輯與異常處理之間有很大程度上的纏繞。

2. 基本異常

異常清醒是指阻止當(dāng)前方法或者作用域繼續(xù)執(zhí)行的問題.
在java中異常同樣也是對象, 使用new可以在堆上創(chuàng)建異常對象
舉個異常最常見的例子好了, 我們都知道除數(shù)是不能為零的, , 所以做除法之前要做一個必要的檢查, 檢查除數(shù)不能為零, 否則將拋出異常
另一種情況就是當(dāng)使用一個對象的時候, 這個對象可能為null, 并沒有初始化, 遇到這樣的情況就需要拋出異常:

if (t == null)
?throw newNull PointerExcept();

2.1 異常參數(shù)

所有標(biāo)準(zhǔn)的異常類都有兩個構(gòu)造器, 一個是默認(rèn)構(gòu)造器, 另一個是字符串為參數(shù), 以便能把相關(guān)信息方法異常對象的構(gòu)造器

3 捕獲異常

3.1 try塊

如果在方法內(nèi)部拋出了異常, 那么這個方法將在拋出異常的過程中結(jié)束, 如果不想方法結(jié)束, 需要將可能產(chǎn)生異常的代碼放入try代碼塊中, 如果產(chǎn)生異常就會被try塊捕獲

3.2 異常處理程序

當(dāng)異常拋出后, 需要被處理, 處理需要在catch塊中進(jìn)行, 對于每個catch自居, 接收一個且僅接收一個特殊類型的的參數(shù)的方法, catch塊就是異常處理程序, 且異常處理程序必須緊跟try

4 創(chuàng)建自定義異常

自定義的異常類必須繼承異常類, 最簡單的就是繼承Exception類了, 來看一個簡單的示例:

class SimpleException extends Exception {}

public class InheritingExceptions {
    public void f() throws SimpleException {
        System.out.println("Throw SimpleException from f()");
        throw new SimpleException();
    }

    public static void main(String[] args) {
        InheritingExceptions sed = new InheritingExceptions();
        try {
            sed.f();
        } catch (SimpleException e) {
            System.out.println("Caught it");
        }
    }
}

Throw SimpleException from f()
Caught it

下面的例子將結(jié)果打印到了控制臺上了, 更好的做法是答應(yīng)道System.err, 因?yàn)镾ystem.out可能會被重定向, 同時這個例子也展示了一個接受字符串參數(shù)的構(gòu)造器:

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

public class FullConstructors {
    public 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()");
    }

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

輸出結(jié)果:

Throwing MyException from f()
MyException
    at FullConstructors.f(FullConstructors.java:11)
    at FullConstructors.main(FullConstructors.java:20)
Throwing MyException from g()
MyException: Originated in g()
    at FullConstructors.g(FullConstructors.java:15)
    at FullConstructors.main(FullConstructors.java:25)

說一下這個printStackTrace()方法, 這個方法將打印"從方法調(diào)用出知道異常跑出處"的方法調(diào)用序列, 這里是將信息發(fā)送到了System.out, 無參數(shù)版本就是答應(yīng)道標(biāo)準(zhǔn)錯誤流

4 異常與記錄日志

使用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);
        }
    }
}

輸出如下

十二月 01, 2016 9:14:31 下午 LoggingException <init>
嚴(yán)重: LoggingException
    at LoggingExceptions.main(LoggingExceptions.java:16)

Caught LoggingException
十二月 01, 2016 9:14:31 下午 LoggingException <init>
嚴(yán)重: LoggingException
    at LoggingExceptions.main(LoggingExceptions.java:23)

Caught LoggingException

靜態(tài)的Logger.getLogger()方法創(chuàng)建了一個String參數(shù)相關(guān)的Logger對象, 通常與錯誤相關(guān)的類名一致, 這個Logger對象會將其輸出發(fā)送到System.err.
severe()方法表示級別, 很明顯, severe表示嚴(yán)重的
但是更常見的情況是我們需要捕獲和記錄別人編寫的異常, 比如說這樣:

import java.util.logging.*;
import java.io.*;

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

輸出結(jié)果為:

十二月 01, 2016 9:24:49 下午 LoggingException2 logException
嚴(yán)重: java.lang.NullPointerException
    at LoggingException2.main(LoggingException2.java:14)

當(dāng)然可以進(jìn)一步自定義異常, 比如加入額外的構(gòu)造器和成員, 得到更強(qiáng)大的功能.

5.異常說明

使用throws關(guān)鍵字, 緊跟在方法后面, 就像上面的程序例子一樣, 說明了這個方法可能會拋出的異常類型, 這就是異常說明

6. 捕獲所有的異常

可以只使用一個異常處理程序來捕獲所有類型的異常, 因?yàn)楫惓n愋偷幕惗际荅xception;

catch(Exception e) {
    System.out.println("Caught an Exception");
}

6.1 棧軌跡

printStackTrace()方法所提供的信息可以通過getStackTrace()方法來直接訪問, 這個方法返回一個由棧軌跡中的元素所構(gòu)成的數(shù)組, 其中每一個元素表示棧中的一幀, 元素0是棧頂元素, 并且是調(diào)用序列中最后一個被調(diào)用的方法, 數(shù)組中的最后一個元素是調(diào)用序列中的第一個方法
下面是一個簡單的演示程序:

public class WhoCalled {
    static void f() {
        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();
    }
}

輸出結(jié)果:

f
main
--------------------------------------
f
g
main
--------------------------------------
f
g
h
main

6.2 重新拋出異常

有時候需要將剛捕獲的異常重新拋出, 尤其是使用Exception捕獲所有異常的時候, 可以直接把它重新拋出, 重新拋出異常會把異常拋給上一級環(huán)境中的異常處理程序, 此外異常對象的所有信息都得以保存.
如果只是重新把當(dāng)前的異常對象拋出, 那么printStrackTrace()方法現(xiàn)實(shí)的將是原來異常拋出點(diǎn)的調(diào)用棧信息, 并非重新拋出點(diǎn)的信息.

public class Rethrowing {
    public static void f() throws Exception {
        System.out.println("Originating the exception in f()");
        throw new Exception("thrown from f()");
    }

    public static void g() throws Exception {
        try {
            f();
        } catch (Exception e) {
            System.out.println("Inside g(), e.printStackTrace()");
            e.printStackTrace(System.out);
            throw e;
        }
    }

    public static void h() throws Exception {
        try {
            g();
        } catch (Exception e) {
            System.out.println("inside h(), e.printStackTrace()");
            e.printStackTrace(System.out);
            // throw (Exception) e.fillInStackTrace();
            throw e;
        }
    }
    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);
        }
    }

}

首先執(zhí)行這個結(jié)果

Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.main(Rethrowing.java:29)
main : printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.main(Rethrowing.java:29)
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.h(Rethrowing.java:19)
    at Rethrowing.main(Rethrowing.java:36)
inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.h(Rethrowing.java:19)
    at Rethrowing.main(Rethrowing.java:36)
main : printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.h(Rethrowing.java:19)
    at Rethrowing.main(Rethrowing.java:36)

去掉注釋并且將throw e;注釋掉, 結(jié)果是:

Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.main(Rethrowing.java:29)
main : printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.main(Rethrowing.java:29)
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.h(Rethrowing.java:19)
    at Rethrowing.main(Rethrowing.java:36)
inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.f(Rethrowing.java:4)
    at Rethrowing.g(Rethrowing.java:9)
    at Rethrowing.h(Rethrowing.java:19)
    at Rethrowing.main(Rethrowing.java:36)
main : printStackTrace()
java.lang.Exception: thrown from f()
    at Rethrowing.h(Rethrowing.java:23)
    at Rethrowing.main(Rethrowing.java:36)

對比第二個結(jié)果比第一個結(jié)果少了倒數(shù)三四行, fillInStackTrace方法會將棧軌跡清空并填充當(dāng)前棧軌跡

6.3 異常鏈

常常會想要在捕獲一個異常之后拋出另一個異常, 并想吧原始異常的信息保存下來, 這樣稱為異常鏈, throwable中initCause()方法可以接受一個cause對象作為參數(shù), 這個cause就是用來表示原始異常的, 這樣通過把原始異常傳遞給新的異常, 是的即使在當(dāng)前位置創(chuàng)建并拋出了新的異常, 也能通過這個異常鏈追蹤到最初發(fā)生異常的地方.
下面看一個可以動態(tài)的擴(kuò)展字段的dome:

class DynamicFieldsException extends Exception{}

public class DynamicFields {
    private Object[][] fields;

    public DynamicFields(int initialSize) {
        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[] obj : fields) {
            result.append(obj[0]);
            result.append(":");
            result.append(obj[1]);
            result.append("\n");
        }
        return result.toString();
    }
    // 判斷是否存在該字段, 存在返回下標(biāo), 如果不存在該字段返回-1
    private int hasField(String id) {
        for (int i = 0; i < fields.length; i++) {
            if (id.equals(fields[i][0])) {
                return i;
            }
        }
        return -1;
    }
    // 返回該字段標(biāo)志符的下標(biāo), 如果不存在就拋出異常
    private int getFieldNumber(String id) throws NoSuchFieldException {
        int fieldNum = hasField(id);
        if (fieldNum == -1) {
            throw new NoSuchFieldException();
        }
        return fieldNum;
    }

    // 動態(tài)的擴(kuò)展
    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];
        }
        for (int i = fields.length; i < temp.length; i++) {
            temp[i] = new Object[] {null, null};
        }
        fields = temp;
        return makeField(id);
    }
    
    // 得到該字段標(biāo)識符的對應(yīng)的字段值
    public Object getField(String id) throws NoSuchFieldException {
        return fields[getFieldNumber(id)][1];
    }
    // 設(shè)置字段
    public Object setField(String id, Object value) throws DynamicFieldsException {
        // 字段值為空的話
        if (value == null) {
            DynamicFieldsException dfe = new DynamicFieldsException();
            // 將此throwable的case初始化為指定值
            dfe.initCause(new NullPointerException());
            throw dfe;
        }
        int fieldNumber = hasField(id);
        // 如果該字段對應(yīng)字段標(biāo)識符不存在的話
        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;
    }

    public static void main(String[] args) {
        DynamicFields df = new DynamicFields(3);
        System.out.println(df);
        try {
            df.setField("d", "A value for d");
            df.setField("number", 47);
            df.setField("number2", 48);
            System.out.println(df);
            df.setField("d", "A new value for d");
            df.setField("number3", 11);
            System.out.println(df);
            System.out.println("df.getField(\"d\")" + df.getField("d"));
            Object field = df.setField("d", null);      // 產(chǎn)生異常
        } catch (NoSuchFieldException e) {
            e.printStackTrace(System.out);
        } catch (DynamicFieldsException e) {
            e.printStackTrace(System.out);
        }
    }
}

運(yùn)行后的輸出結(jié)果為:

null:null
null:null
null:null

d:A value for d
number:47
number2:48

d:A new value for d
number:47
number2:48
number3:11

df.getField("d")A new value for d
DynamicFieldsException
    at DynamicFields.setField(DynamicFields.java:64)
    at DynamicFields.main(DynamicFields.java:94)
Caused by: java.lang.NullPointerException
    at DynamicFields.setField(DynamicFields.java:65)
    ... 1 more
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容