1. 程式人生 > >【轉載】java7的異常處理新特性

【轉載】java7的異常處理新特性

原文地址:https://my.oschina.net/fhd/blog/324484
前輩們非常給力!

addSuppressed()方法

開發人員對異常處理的try-catch-finally語句塊都比較熟悉。如果在try語句塊中丟擲了異常,在控制權轉移到呼叫棧上一層程式碼之前,finally語句塊中的語句也會執行。但是finally語句塊在執行的過程中,也可能會丟擲異常。如果finally語句塊也丟擲了異常,那麼這個異常會往上傳遞,而之前try語句塊中的那個異常就丟失了。如例:

package test;

public class DisappearedException {
    public void show() throws BaseException {
        try {
            Integer.parseInt("Hello");
        } catch (NumberFormatException e1) {
            throw new BaseException(e1);
        } finally {
            try {
                int result = 2 / 0;
            } catch (ArithmeticException e2) {
                throw new BaseException(e2);
            }
        }
    }
    public static void main(String[] args) throws Exception {
        DisappearedException d = new DisappearedException();
        d.show();
    }
}

class BaseException extends Exception {
    public BaseException(Exception ex){
        super(ex);
    }
    private static final long serialVersionUID = 3987852541476867869L;
}

對這種問題的解決辦法一般有兩種:一種是丟擲try語句塊中產生的原始異常,忽略在finally語句塊中產生的異常。這麼做的出發點是try語句塊中的異常才是問題的根源。如例:

package test;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadFile {
    public static void main(String[] args) {
        ReadFile rf = new ReadFile();
        try {
            rf.read("F:/manifest_provider_loophole.txt");
        } catch (BaseException2 e) {
            e.printStackTrace();
        }
    }
    public void read(String filename) throws BaseException2 {
        FileInputStream input = null;
        IOException readException = null;
        try {
            input = new FileInputStream(filename);
        } catch (IOException ex) {
            readException = ex;
        } finally {
            if(input != null){
                try {
                    input.close();
                } catch (IOException ex2) {
                    if(readException == null){
                        readException = ex2;
                    }
                }
            }
            if(readException != null){
                throw new BaseException2(readException); 
            }
        }
    }
}

class BaseException2 extends Exception {
    private static final long serialVersionUID = 5062456327806414216L;
    public BaseException2(Exception ex){
        super(ex);
    }
}

另外一種是把產生的異常都記錄下來。這麼做的好處是不會丟失任何異常。在java7之前,這種做法需要實現自己的異常類,而在java7中,已經對Throwable類進行了修改以支援這種情況。在java7中為Throwable類增加addSuppressed方法。當一個異常被丟擲的時候,可能有其他異常因為該異常而被抑制住,從而無法正常丟擲。這時可以通過addSuppressed方法把這些被抑制的方法記錄下來。被抑制的異常會出現在丟擲的異常的堆疊資訊中,也可以通過getSuppressed方法來獲取這些異常。這樣做的好處是不會丟失任何異常,方便開發人員進行除錯。如例:

package test;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadFile2 {
    public static void main(String[] args) {
        ReadFile rf = new ReadFile();
        try {
            rf.read("F:/manifest_provider_loophole.txt");
        } catch (BaseException2 e) {
            e.printStackTrace();
        }
    }
    public void read(String filename) throws IOException {
        FileInputStream input = null;
        IOException readException = null;
        try {
            input = new FileInputStream(filename);
        } catch (IOException ex) {
            readException = ex;
        } finally {
            if(input != null){
                try {
                    input.close();
                } catch (IOException ex2) {
                    if(readException != null){
                        readException.addSuppressed(ex2);    //注意這裡
                    }else{
                        readException = ex2;
                    }
                }
            }
            if(readException != null){
                throw readException;
            }
        }
    }
}

這種做法的關鍵在於把finally語句中產生的異常通過 addSuppressed方法加到try語句產生的異常中。

一個catch子句捕獲多個異常

在Java7之前的異常處理語法中,一個catch子句只能捕獲一類異常。在要處理的異常種類很多時這種限制會很麻煩。每一種異常都需要新增一個catch子句,而且這些catch子句中的處理邏輯可能都是相同的,從而會造成程式碼重複。雖然可以在catch子句中通過這些異常的基類來捕獲所有的異常,比如使用Exception作為捕獲的型別,但是這要求對這些不同的異常所做的處理是相同的。另外也可能捕獲到某些不應該被捕獲的非受檢查異常。而在某些情況下,程式碼重複是不可避免的。比如某個方法可能丟擲4種不同的異常,其中有2種異常使用相同的處理方式,另外2種異常的處理方式也相同,但是不同於前面的2種異常。這勢必會在catch子句中包含重複的程式碼。

對於這種情況,Java7改進了catch子句的語法,允許在其中指定多種異常,每個異常型別之間使用“|”來分隔。如例:

package test;

public class ExceptionHandler {
    public void handle(){
        try {
            //..............
        } catch (ExceptionA | ExceptionB ab) { 
        } catch (ExceptionC c) {    
        }
    }
}

這種新的處理方式使上面提出的問題得到了很好的解決。需要注意的是,在catch子句中宣告捕獲的這些異常類中,不能出現重複的型別,也不允許其中的某個異常是另外一個異常的子類,否則會出現編譯錯誤。如果在catch子句中聲明瞭多個異常類,那麼異常引數的具體型別是所有這些異常型別的最小上界。

關於一個catch子句中的異常型別不能出現其中一個是另外一個的子類的情況,實際上涉及捕獲多個異常的內部實現方式。比如:

public void testSequence() {
    try {
        Integer.parseInt("Hello");
    } catch (NumberFormatException | RuntimeException e){}
}

比如上面這段程式碼,雖然NumberFormatException是RuntimeException的子類,但是這段程式碼是可以通過編譯的。但是,如果把catch子句中兩個異常的宣告位置調換一下,就會出現在編譯錯誤。如例:

public void testSequenceError() {
    try {
        Integer.parseInt("Hello");
    } catch (RuntimeException | NumberFormatException e) {}
}

原因在於,編譯器的做法其實是把捕獲多個異常的catch子句轉換成了多個catch子句,在每個catch子句中捕獲一個異常。上面這段程式碼相當於:

public void testSequenceError() {
    try {
        Integer.parseInt("Hello");
    } catch (RuntimeException e) {
    } catch (NumberFormatException e) {
    }
}