夯實Java基礎系列10:深刻理解Java中的異常體系

目錄


- Java異常

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看java

https://github.com/h2pl/Java-...

喜歡的話麻煩點下Star哈mysql

文章首發於個人我的博客:git

www.how2playlife.com

本文是微信公衆號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部份內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了不少我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,若有侵權,請聯繫做者。
該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着瞭解每一個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,造成本身的知識框架。爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每一個知識點對應的面試題以及參考答案。程序員

若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。github

<!-- more -->面試

爲何要使用異常

首先咱們能夠明確一點就是異常的處理機制能夠確保咱們程序的健壯性,提升系統可用率。雖然咱們不是特別喜歡看到它,可是咱們不能不認可它的地位,做用。

在沒有異常機制的時候咱們是這樣處理的:經過函數的返回值來判斷是否發生了異常(這個返回值一般是已經約定好了的),調用該函數的程序負責檢查而且分析返回值。雖然能夠解決異常問題,可是這樣作存在幾個缺陷:算法

一、 容易混淆。若是約定返回值爲-11111時表示出現異常,那麼當程序最後的計算結果然的爲-1111呢?

二、 代碼可讀性差。將異常處理代碼和程序代碼混淆在一塊兒將會下降代碼的可讀性。sql

三、 由調用函數來分析異常,這要求程序員對庫函數有很深的瞭解。數據庫

在OO中提供的異常處理機制是提供代碼健壯的強有力的方式。使用異常機制它可以下降錯誤處理代碼的複雜度,若是不使用異常,那麼就必須檢查特定的錯誤,並在程序中的許多地方去處理它。

而若是使用異常,那就沒必要在方法調用處進行檢查,由於異常機制將保證可以捕獲這個錯誤,而且,只需在一個地方處理錯誤,即所謂的異常處理程序中。

這種方式不只節約代碼,並且把「概述在正常執行過程當中作什麼事」的代碼和「出了問題怎麼辦」的代碼相分離。總之,與之前的錯誤處理方法相比,異常機制使代碼的閱讀、編寫和調試工做更加層次分明。(摘自《Think in java 》)。

該部份內容選自http://www.cnblogs.com/chenss...

異常基本定義

在《Think in java》中是這樣定義異常的:異常情形是指阻止當前方法或者做用域繼續執行的問題。在這裏必定要明確一點:異常代碼某種程度的錯誤,儘管Java有異常處理機制,可是咱們不能以「正常」的眼光來看待異常,異常處理機制的緣由就是告訴你:這裏可能會或者已經產生了錯誤,您的程序出現了不正常的狀況,可能會致使程序失敗!

那麼何時纔會出現異常呢?只有在你當前的環境下程序沒法正常運行下去,也就是說程序已經沒法來正確解決問題了,這時它所就會從當前環境中跳出,並拋出異常。拋出異常後,它首先會作幾件事。

首先,它會使用new建立一個異常對象,而後在產生異常的位置終止程序,而且從當前環境中彈出對異常對象的引用,這時。異常處理機制就會接管程序,並開始尋找一個恰當的地方來繼續執行程序,這個恰當的地方就是異常處理程序。

總的來講異常處理機制就是當程序發生異常時,它強制終止程序運行,記錄異常信息並將這些信息反饋給咱們,由咱們來肯定是否處理異常。

異常體系

[外鏈圖片轉存失敗(img-KNxcBTK0-1569073569353)(https://images0.cnblogs.com/b...]

從上面這幅圖能夠看出,Throwable是java語言中全部錯誤和異常的超類(萬物便可拋)。它有兩個子類:Error、Exception。

Java標準庫內建了一些通用的異常,這些類以Throwable爲頂層父類。

Throwable又派生出Error類和Exception類。

錯誤:Error類以及他的子類的實例,表明了JVM自己的錯誤。錯誤不能被程序員經過代碼處理,Error不多出現。所以,程序員應該關注Exception爲父類的分支下的各類異常類。

異常:Exception以及他的子類,表明程序運行時發送的各類不指望發生的事件。能夠被Java異常處理機制使用,是異常處理的核心。

整體上咱們根據Javac對異常的處理要求,將異常類分爲2類。

非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求在程序處理這些異常。因此若是願意,咱們能夠編寫代碼處理(使用try…catch…finally)這樣的異常,也能夠不處理。

對於這些異常,咱們應該修正代碼,而不是去經過異常處理器處理 。這樣的異常發生的緣由多半是代碼寫的有問題。如除0錯誤ArithmeticException,錯誤的強制類型轉換錯誤ClassCastException,數組索引越界ArrayIndexOutOfBoundsException,使用了空對象NullPointerException等等。

檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強制要求程序員爲這樣的異常作預備處理工做(使用try…catch…finally或者throws)。在方法中要麼用try-catch語句捕獲它並處理,要麼用throws子句聲明拋出它,不然編譯不會經過。

這樣的異常通常是由程序的運行環境致使的。由於程序可能被運行在各類未知的環境下,而程序員沒法干預用戶如何使用他編寫的程序,因而程序員就應該爲這樣的異常時刻準備着。如SQLException , IOException,ClassNotFoundException 等。

須要明確的是:檢查和非檢查是對於javac來講的,這樣就很好理解和區分了。

這部份內容摘自http://www.importnew.com/2661...

初識異常

異常是在執行某個函數時引起的,而函數又是層級調用,造成調用棧的,由於,只要一個函數發生了異常,那麼他的全部的caller都會被異常影響。當這些被影響的函數以異常信息輸出時,就造成的了異常追蹤棧。

異常最早發生的地方,叫作異常拋出點。

public class 異常 {
    public static void main (String [] args )
    {
        System . out. println( "----歡迎使用命令行除法計算器----" ) ;
        CMDCalculate ();
    }
    public static void CMDCalculate ()
    {
        Scanner scan = new Scanner ( System. in );
        int num1 = scan .nextInt () ;
        int num2 = scan .nextInt () ;
        int result = devide (num1 , num2 ) ;
        System . out. println( "result:" + result) ;
        scan .close () ;
    }
    public static int devide (int num1, int num2 ){
        return num1 / num2 ;
    }

//    ----歡迎使用命令行除法計算器----
//            1
//            0
//    Exception in thread "main" java.lang.ArithmeticException: / by zero
//    at com.javase.異常.異常.devide(異常.java:24)
//    at com.javase.異常.異常.CMDCalculate(異常.java:19)
//    at com.javase.異常.異常.main(異常.java:12)

//  ----歡迎使用命令行除法計算器----
//    r
//    Exception in thread "main" java.util.InputMismatchException
//    at java.util.Scanner.throwFor(Scanner.java:864)
//    at java.util.Scanner.next(Scanner.java:1485)
//    at java.util.Scanner.nextInt(Scanner.java:2117)
//    at java.util.Scanner.nextInt(Scanner.java:2076)
//    at com.javase.異常.異常.CMDCalculate(異常.java:17)
//    at com.javase.異常.異常.main(異常.java:12)

[外鏈圖片轉存失敗(img-9rqUQJQj-1569073569354)(http://incdn1.b0.upaiyun.com/...]

從上面的例子能夠看出,當devide函數發生除0異常時,devide函數將拋出ArithmeticException異常,所以調用他的CMDCalculate函數也沒法正常完成,所以也發送異常,而CMDCalculate的caller——main 由於CMDCalculate拋出異常,也發生了異常,這樣一直向調用棧的棧底回溯。

這種行爲叫作異常的冒泡,異常的冒泡是爲了在當前發生異常的函數或者這個函數的caller中找到最近的異常處理程序。因爲這個例子中沒有使用任何異常處理機制,所以異常最終由main函數拋給JRE,致使程序終止。

上面的代碼不使用異常處理機制,也能夠順利編譯,由於2個異常都是非檢查異常。可是下面的例子就必須使用異常處理機制,由於異常是檢查異常。

代碼中我選擇使用throws聲明異常,讓函數的調用者去處理可能發生的異常。可是爲何只throws了IOException呢?由於FileNotFoundException是IOException的子類,在處理範圍內。

異常和錯誤

下面看一個例子

//錯誤即error通常指jvm沒法處理的錯誤
//異常是Java定義的用於簡化錯誤處理流程和定位錯誤的一種工具。
public class 錯誤和錯誤 {
    Error error = new Error();

    public static void main(String[] args) {
        throw new Error();
    }

    //下面這四個異常或者錯誤有着不一樣的處理方法
    public void error1 (){
        //編譯期要求必須處理,由於這個異常是最頂層異常,包括了檢查異常,必需要處理
        try {
            throw new Throwable();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
    //Exception也必須處理。不然報錯,由於檢查異常都繼承自exception,因此默認須要捕捉。
    public void error2 (){
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //error能夠不處理,編譯不報錯,緣由是虛擬機根本沒法處理,因此啥都不用作
    public void error3 (){
        throw new Error();
    }

    //runtimeexception衆所周知編譯不會報錯
    public void error4 (){
        throw new RuntimeException();
    }
//    Exception in thread "main" java.lang.Error
//    at com.javase.異常.錯誤.main(錯誤.java:11)

}

異常的處理方式

在編寫代碼處理異常時,對於檢查異常,有2種不一樣的處理方式:

使用try…catch…finally語句塊處理它。

或者,在函數簽名中使用throws 聲明交給函數調用者caller去解決。

下面看幾個具體的例子,包括error,exception和throwable

上面的例子是運行時異常,不須要顯示捕獲。
下面這個例子是可檢查異常需,要顯示捕獲或者拋出。

@Test
public void testException() throws IOException
{
    //FileInputStream的構造函數會拋出FileNotFoundException
    FileInputStream fileIn = new FileInputStream("E:\\a.txt");

    int word;
    //read方法會拋出IOException
    while((word =  fileIn.read())!=-1)
    {
        System.out.print((char)word);
    }
    //close方法會拋出IOException
    fileIn.close();
}

通常狀況下的處理方式 try catch finally

public class 異常處理方式 {

@Test
public void main() {
    try{
        //try塊中放可能發生異常的代碼。
        InputStream inputStream = new FileInputStream("a.txt");

        //若是執行完try且不發生異常,則接着去執行finally塊和finally後面的代碼(若是有的話)。
        int i = 1/0;
        //若是發生異常,則嘗試去匹配catch塊。
        throw new SQLException();
        //使用1.8jdk同時捕獲多個異常,runtimeexception也能夠捕獲。只是捕獲後虛擬機也沒法處理,因此不建議捕獲。
    }catch(SQLException | IOException | ArrayIndexOutOfBoundsException exception){
        System.out.println(exception.getMessage());
        //每個catch塊用於捕獲並處理一個特定的異常,或者這異常類型的子類。Java7中能夠將多個異常聲明在一個catch中。

        //catch後面的括號定義了異常類型和異常參數。若是異常與之匹配且是最早匹配到的,則虛擬機將使用這個catch塊來處理異常。

        //在catch塊中可使用這個塊的異常參數來獲取異常的相關信息。異常參數是這個catch塊中的局部變量,其它塊不能訪問。

        //若是當前try塊中發生的異常在後續的全部catch中都沒捕獲到,則先去執行finally,而後到這個函數的外部caller中去匹配異常處理器。

        //若是try中沒有發生異常,則全部的catch塊將被忽略。

    }catch(Exception exception){
        System.out.println(exception.getMessage());
        //...
    }finally{
        //finally塊一般是可選的。
        //不管異常是否發生,異常是否匹配被處理,finally都會執行。

        //finally主要作一些清理工做,如流的關閉,數據庫鏈接的關閉等。
    }

一個try至少要跟一個catch或者finally

try {
        int i = 1;
    }finally {
        //一個try至少要有一個catch塊,不然, 至少要有1個finally塊。可是finally不是用來處理異常的,finally不會捕獲異常。
    }
}

異常出現時該方法後面的代碼不會運行,即便異常已經被捕獲。這裏舉出一個奇特的例子,在catch裏再次使用try catch finally

@Test
public void test() {
    try {
        throwE();
        System.out.println("我前面拋出異常了");
        System.out.println("我不會執行了");
    } catch (StringIndexOutOfBoundsException e) {
        System.out.println(e.getCause());
    }catch (Exception ex) {
    //在catch塊中仍然可使用try catch finally
        try {
            throw new Exception();
        }catch (Exception ee) {
            
        }finally {
            System.out.println("我所在的catch塊沒有執行,我也不會執行的");
        }
    }
}
//在方法聲明中拋出的異常必須由調用方法處理或者繼續往上拋,
// 當拋到jre時因爲沒法處理終止程序
public void throwE (){
//        Socket socket = new Socket("127.0.0.1", 80);

        //手動拋出異常時,不會報錯,可是調用該方法的方法須要處理這個異常,不然會出錯。
//        java.lang.StringIndexOutOfBoundsException
//        at com.javase.異常.異常處理方式.throwE(異常處理方式.java:75)
//        at com.javase.異常.異常處理方式.test(異常處理方式.java:62)
        throw new StringIndexOutOfBoundsException();
    }

其實有的語言在遇到異常後仍然能夠繼續運行

有的編程語言當異常被處理後,控制流會恢復到異常拋出點接着執行,這種策略叫作:resumption model of exception handling(恢復式異常處理模式 )

而Java則是讓執行流恢復處處理了異常的catch塊後接着執行,這種策略叫作:termination model of exception handling(終結式異常處理模式)

"不負責任"的throws

throws是另外一種處理異常的方式,它不一樣於try…catch…finally,throws僅僅是將函數中可能出現的異常向調用者聲明,而本身則不具體處理。

採起這種異常處理的緣由多是:方法自己不知道如何處理這樣的異常,或者說讓調用者處理更好,調用者須要爲可能發生的異常負責。

public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{ 
     //foo內部能夠拋出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 類的異常,或者他們的子類的異常對象。
}

糾結的finally

finally塊無論異常是否發生,只要對應的try執行了,則它必定也執行。只有一種方法讓finally塊不執行:System.exit()。所以finally塊一般用來作資源釋放操做:關閉文件,關閉數據庫鏈接等等。

良好的編程習慣是:在try塊中打開資源,在finally塊中清理釋放這些資源。

須要注意的地方:

一、finally塊沒有處理異常的能力。處理異常的只能是catch塊。

二、在同一try…catch…finally塊中 ,若是try中拋出異常,且有匹配的catch塊,則先執行catch塊,再執行finally塊。若是沒有catch塊匹配,則先執行finally,而後去外面的調用者中尋找合適的catch塊。

三、在同一try…catch…finally塊中 ,try發生異常,且匹配的catch塊中處理異常時也拋出異常,那麼後面的finally也會執行:首先執行finally塊,而後去外圍調用者中尋找合適的catch塊。

public class finally使用 {
    public static void main(String[] args) {
        try {
            throw new IllegalAccessException();
        }catch (IllegalAccessException e) {
            // throw new Throwable();
            //此時若是再拋異常,finally沒法執行,只能報錯。
            //finally不管什麼時候都會執行
            //除非我顯示調用。此時finally纔不會執行
            System.exit(0);

        }finally {
            System.out.println("算你狠");
        }
    }
}

throw : JRE也使用的關鍵字

throw exceptionObject

程序員也能夠經過throw語句手動顯式的拋出一個異常。throw語句的後面必須是一個異常對象。

throw 語句必須寫在函數中,執行throw 語句的地方就是一個異常拋出點,==它和由JRE自動造成的異常拋出點沒有任何差異。==

public void save(User user)
{
      if(user  == null) 
          throw new IllegalArgumentException("User對象爲空");
      //......
 
}

後面開始的大部份內容都摘自http://www.cnblogs.com/lulipr...

該文章寫的十分細緻到位,使人欽佩,是我目前爲之看到關於異常最詳盡的文章,能夠說是站在巨人的肩膀上了。

異常調用鏈

異常的鏈化

在一些大型的,模塊化的軟件開發中,一旦一個地方發生異常,則如骨牌效應同樣,將致使一連串的異常。假設B模塊完成本身的邏輯須要調用A模塊的方法,若是A模塊發生異常,則B也將不能完成而發生異常。

==可是B在拋出異常時,會將A的異常信息掩蓋掉,這將使得異常的根源信息丟失。異常的鏈化能夠將多個模塊的異常串聯起來,使得異常信息不會丟失。==

異常鏈化:以一個異常對象爲參數構造新的異常對象。新的異對象將包含先前異常的信息。這項技術主要是異常類的一個帶Throwable參數的函數來實現的。這個當作參數的異常,咱們叫他根源異常(cause)。

查看Throwable類源碼,能夠發現裏面有一個Throwable字段cause,就是它保存了構造時傳遞的根源異常參數。這種設計和鏈表的結點類設計一模一樣,所以造成鏈也是天然的了。

public class Throwable implements Serializable {
    private Throwable cause = this;
 
    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
     public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
 
    //........
}

下面看一個比較實在的異常鏈例子哈

public class 異常鏈 {
    @Test
    public void test() {
        C();
    }
    public void A () throws Exception {
        try {
            int i = 1;
            i = i / 0;
            //當我註釋掉這行代碼並使用B方法拋出一個error時,運行結果以下
//            四月 27, 2018 10:12:30 下午 org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines
//            信息: Discovered TestEngines with IDs: [junit-jupiter]
//            java.lang.Error: B也犯了個錯誤
//            at com.javase.異常.異常鏈.B(異常鏈.java:33)
//            at com.javase.異常.異常鏈.C(異常鏈.java:38)
//            at com.javase.異常.異常鏈.test(異常鏈.java:13)
//            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
//            Caused by: java.lang.Error
//            at com.javase.異常.異常鏈.B(異常鏈.java:29)

        }catch (ArithmeticException e) {
            //這裏經過throwable類的構造方法將最底層的異常從新包裝並拋出,此時注入了A方法的信息。最後打印棧信息時能夠看到caused by
            A方法的異常。
            //若是直接拋出,棧信息打印結果只能看到上層方法的錯誤信息,不能看到實際上是A發生了錯誤。
            //因此須要包裝並拋出
            throw new Exception("A方法計算錯誤", e);
        }

    }
    public void B () throws Exception,Error {
        try {
            //接收到A的異常,
            A();
            throw new Error();
        }catch (Exception e) {
            throw e;
        }catch (Error error) {
            throw new Error("B也犯了個錯誤", error);
        }
    }
    public void C () {
        try {
            B();
        }catch (Exception | Error e) {
            e.printStackTrace();
        }

    }

    //最後結果
//    java.lang.Exception: A方法計算錯誤
//    at com.javase.異常.異常鏈.A(異常鏈.java:18)
//    at com.javase.異常.異常鏈.B(異常鏈.java:24)
//    at com.javase.異常.異常鏈.C(異常鏈.java:31)
//    at com.javase.異常.異常鏈.test(異常鏈.java:11)
//    省略
//    Caused by: java.lang.ArithmeticException: / by zero
//    at com.javase.異常.異常鏈.A(異常鏈.java:16)
//            ... 31 more
}

自定義異常

若是要自定義異常類,則擴展Exception類便可,所以這樣的自定義異常都屬於檢查異常(checked exception)。若是要自定義非檢查異常,則擴展自RuntimeException。

按照國際慣例,自定義的異常應該老是包含以下的構造函數:

一個無參構造函數
一個帶有String參數的構造函數,並傳遞給父類的構造函數。
一個帶有String參數和Throwable參數,並都傳遞給父類構造函數
一個帶有Throwable 參數的構造函數,並傳遞給父類的構造函數。
下面是IOException類的完整源代碼,能夠借鑑。

public class IOException extends Exception
{
    static final long serialVersionUID = 7818375828146090155L;
 
    public IOException()
    {
        super();
    }
 
    public IOException(String message)
    {
        super(message);
    }
 
    public IOException(String message, Throwable cause)
    {
        super(message, cause);
    }
 
    public IOException(Throwable cause)
    {
        super(cause);
    }
}

異常的注意事項

異常的注意事項

當子類重寫父類的帶有 throws聲明的函數時,其throws聲明的異常必須在父類異常的可控範圍內——用於處理父類的throws方法的異常處理器,必須也適用於子類的這個帶throws方法 。這是爲了支持多態。

例如,父類方法throws 的是2個異常,子類就不能throws 3個及以上的異常。父類throws IOException,子類就必須throws IOException或者IOException的子類。

至於爲何?我想,也許下面的例子能夠說明。

class Father
{
    public void start() throws IOException
    {
        throw new IOException();
    }
}
 
class Son extends Father
{
    public void start() throws Exception
    {
        throw new SQLException();
    }
}

/假設上面的代碼是容許的(實質是錯誤的)*/

class Test
{
    public static void main(String[] args)
    {
        Father[] objs = new Father[2];
        objs[0] = new Father();
        objs[1] = new Son();
 
        for(Father obj:objs)
        {
        //由於Son類拋出的實質是SQLException,而IOException沒法處理它。
        //那麼這裏的try。。catch就不能處理Son中的異常。
        //多態就不能實現了。
            try {
                 obj.start();
            }catch(IOException)
            {
                 //處理IOException
            }
         }
   }
}

==Java的異常執行流程是線程獨立的,線程之間沒有影響==

Java程序能夠是多線程的。每個線程都是一個獨立的執行流,獨立的函數調用棧。若是程序只有一個線程,那麼沒有被任何代碼處理的異常 會致使程序終止。若是是多線程的,那麼沒有被任何代碼處理的異常僅僅會致使異常所在的線程結束。

也就是說,Java中的異常是線程獨立的,線程的問題應該由線程本身來解決,而不要委託到外部,也不會直接影響到其它線程的執行。

下面看一個例子

public class 多線程的異常 {
    @Test
    public void test() {
        go();
    }
    public void go () {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0;i <= 2;i ++) {
            int finalI = i;
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(new Runnable() {
                @Override
                //每一個線程拋出異常時並不會影響其餘線程的繼續執行
                public void run() {
                    try {
                        System.out.println("start thread" + finalI);
                        throw new Exception();
                    }catch (Exception e) {
                        System.out.println("thread" + finalI + " go wrong");
                    }
                }
            });
        }
//        結果:
//        start thread0
//        thread0 go wrong
//        start thread1
//        thread1 go wrong
//        start thread2
//        thread2 go wrong
    }
}

當finally趕上return

首先一個不容易理解的事實:

在 try塊中即使有return,break,continue等改變執行流的語句,finally也會執行。

public static void main(String[] args)
{
    int re = bar();
    System.out.println(re);
}
private static int bar() 
{
    try{
        return 5;
    } finally{
        System.out.println("finally");
    }
}
/*輸出:
finally
*/

不少人面對這個問題時,老是在概括執行的順序和規律,不過我以爲仍是很難理解。我本身總結了一個方法。用以下GIF圖說明。

[外鏈圖片轉存失敗(img-SceF4t85-1569073569354)(http://incdn1.b0.upaiyun.com/...]

也就是說:try…catch…finally中的return 只要能執行,就都執行了,他們共同向同一個內存地址(假設地址是0×80)寫入返回值,後執行的將覆蓋先執行的數據,而真正被調用者取的返回值就是最後一次寫入的。那麼,按照這個思想,下面的這個例子也就不難理解了。

finally中的return 會覆蓋 try 或者catch中的返回值。

public static void main(String[] args)
    {
        int result;
 
        result  =  foo();
        System.out.println(result);     /////////2
 
        result = bar();
        System.out.println(result);    /////////2
    }
 
    @SuppressWarnings("finally")
    public static int foo()
    {
        trz{
            int a = 5 / 0;
        } catch (Exception e){
            return 1;
        } finally{
            return 2;
        }
 
    }
 
    @SuppressWarnings("finally")
    public static int bar()
    {
        try {
            return 1;
        }finally {
            return 2;
        }
    }

finally中的return會抑制(消滅)前面try或者catch塊中的異常

class TestException
{
    public static void main(String[] args)
    {
        int result;
        try{
            result = foo();
            System.out.println(result);           //輸出100
        } catch (Exception e){
            System.out.println(e.getMessage());    //沒有捕獲到異常
        }
 
        try{
            result  = bar();
            System.out.println(result);           //輸出100
        } catch (Exception e){
            System.out.println(e.getMessage());    //沒有捕獲到異常
        }
    }
 
    //catch中的異常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("我將被忽略,由於下面的finally中使用了return");
        }finally {
            return 100;
        }
    }
 
    //try中的異常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }finally {
            return 100;
        }
    }
}

finally中的異常會覆蓋(消滅)前面try或者catch中的異常

class TestException
{
    public static void main(String[] args)
    {
        int result;
        try{
            result = foo();
        } catch (Exception e){
            System.out.println(e.getMessage());    //輸出:我是finaly中的Exception
        }
 
        try{
            result  = bar();
        } catch (Exception e){
            System.out.println(e.getMessage());    //輸出:我是finaly中的Exception
        }
    }
 
    //catch中的異常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("我將被忽略,由於下面的finally中拋出了新的異常");
        }finally {
            throw new Exception("我是finaly中的Exception");
        }
    }
 
    //try中的異常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }finally {
            throw new Exception("我是finaly中的Exception");
        }
 
    }
}

上面的3個例子都異於常人的編碼思惟,所以我建議:

不要在fianlly中使用return。

不要在finally中拋出異常。

減輕finally的任務,不要在finally中作一些其它的事情,finally塊僅僅用來釋放資源是最合適的。

將盡可能將全部的return寫在函數的最後面,而不是try … catch … finally中。

JAVA異經常見面試題

  下面是我我的總結的在Java和J2EE開發者在面試中常常被問到的有關Exception和Error的知識。在分享個人回答的時候,我也給這些問題做了快速修訂,而且提供源碼以便深刻理解。我總結了各類難度的問題,適合新手碼農和高級Java碼農。若是你遇到了我列表中沒有的問題,而且這個問題很是好,請在下面評論中分享出來。你也能夠在評論中分享你面試時答錯的狀況。

1) Java中什麼是Exception?
  這個問題常常在第一次問有關異常的時候或者是面試菜鳥的時候問。我歷來沒見過面高級或者資深工程師的時候有人問這玩意,可是對於菜鳥,是很願意問這個的。簡單來講,異常是Java傳達給你的系統和程序錯誤的方式。在java中,異常功能是經過實現好比Throwable,Exception,RuntimeException之類的類,而後還有一些處理異常時候的關鍵字,好比throw,throws,try,catch,finally之類的。 全部的異常都是經過Throwable衍生出來的。Throwable把錯誤進一步劃分爲 java.lang.Exception
和 java.lang.Error.  java.lang.Error 用來處理系統錯誤,例如java.lang.StackOverFlowError 之類的。而後 Exception用來處理程序錯誤,請求的資源不可用等等。

2) Java中的檢查型異常和非檢查型異常有什麼區別?

  這又是一個很是流行的Java異常面試題,會出如今各類層次的Java面試中。檢查型異常和非檢查型異常的主要區別在於其處理方式。檢查型異常須要使用try, catch和finally關鍵字在編譯期進行處理,不然會出現編譯器會報錯。對於非檢查型異常則不須要這樣作。Java中全部繼承自java.lang.Exception類的異常都是檢查型異常,全部繼承自RuntimeException的異常都被稱爲非檢查型異常。

3) Java中的NullPointerException和ArrayIndexOutOfBoundException之間有什麼相同之處?

  在Java異常面試中這並非一個很流行的問題,但會出如今不一樣層次的初學者面試中,用來測試應聘者對檢查型異常和非檢查型異常的概念是否熟悉。順便說一下,該題的答案是,這兩個異常都是非檢查型異常,都繼承自RuntimeException。該問題可能會引出另外一個問題,即Java和C的數組有什麼不一樣之處,由於C裏面的數組是沒有大小限制的,絕對不會拋出ArrayIndexOutOfBoundException。

4)在Java異常處理的過程當中,你遵循的那些最好的實踐是什麼?

  這個問題在面試技術經理是很是常見的一個問題。由於異常處理在項目設計中是很是關鍵的,因此精通異常處理是十分必要的。異常處理有不少最佳實踐,下面列舉集中,它們提升你代碼的健壯性和靈活性:

  1) 調用方法的時候返回布爾值來代替返回null,這樣能夠 NullPointerException。因爲空指針是java異常裏最噁心的異常

  2) catch塊裏別不寫代碼。空catch塊是異常處理裏的錯誤事件,由於它只是捕獲了異常,卻沒有任何處理或者提示。一般你起碼要打印出異常信息,固然你最好根據需求對異常信息進行處理。

  3)能拋受控異常(checked Exception)就儘可能不拋受非控異常(checked Exception)。經過去掉重複的異常處理代碼,能夠提升代碼的可讀性。

  4) 絕對不要讓你的數據庫相關異常顯示到客戶端。因爲絕大多數數據庫和SQLException異常都是受控異常,在Java中,你應該在DAO層把異常信息處理,而後返回處理過的能讓用戶看懂並根據異常提示信息改正操做的異常信息。

  5) 在Java中,必定要在數據庫鏈接,數據庫查詢,流處理後,在finally塊中調用close()方法。

5) 既然咱們能夠用RuntimeException來處理錯誤,那麼你認爲爲何Java中還存在檢查型異常?

  這是一個有爭議的問題,在回答該問題時你應當當心。雖然他們確定願意聽到你的觀點,但其實他們最感興趣的仍是有說服力的理由。我認爲其中一個理由是,存在檢查型異常是一個設計上的決定,受到了諸如C++等比Java更早編程語言設計經驗的影響。絕大多數檢查型異常位於java.io包內,這是合乎情理的,由於在你請求了不存在的系統資源的時候,一段強壯的程序必須可以優雅的處理這種狀況。經過把IOException聲明爲檢查型異常,Java 確保了你可以優雅的對異常進行處理。另外一個可能的理由是,可使用catch或finally來確保數量受限的系統資源(好比文件描述符)在你使用後儘早獲得釋放。 Joshua
Bloch編寫的 Effective Java 一書 中多處涉及到了該話題,值得一讀。

6)  throw 和 throws這兩個關鍵字在java中有什麼不一樣?

  一個java初學者應該掌握的面試問題。 throw 和 throws乍看起來是很類似的尤爲是在你仍是一個java初學者的時候。儘管他們看起來類似,都是在處理異常時候使用到的。但在代碼裏的使用方法和用到的地方是不一樣的。throws老是出如今一個函數頭中,用來標明該成員函數可能拋出的各類異常, 你也能夠申明未檢查的異常,但這不是編譯器強制的。若是方法拋出了異常那麼調用這個方法的時候就須要將這個異常處理。另外一個關鍵字  throw 是用來拋出任意異常的,按照語法你能夠拋出任意 Throwable (i.e. Throwable
或任何Throwable的衍生類) , throw能夠中斷程序運行,所以能夠用來代替return . 最多見的例子是用 throw 在一個空方法中須要return的地方拋出 UnSupportedOperationException 代碼以下 :

123 private`static void show() {thrownew UnsupportedOperationException("Notyet implemented");`}

  能夠看下這篇 文章查看這兩個關鍵字在java中更多的差別 。

7) 什麼是「異常鏈」?

  「異常鏈」是Java中很是流行的異常處理概念,是指在進行一個異常處理時拋出了另一個異常,由此產生了一個異常鏈條。該技術大多用於將「 受檢查異常」 ( checked exception)封裝成爲「非受檢查異常」(unchecked exception)或者RuntimeException。順便說一下,若是由於由於異常你決定拋出一個新的異常,你必定要包含原有的異常,這樣,處理程序才能夠經過getCause()和initCause()方法來訪問異常最終的根源。

) 你曾經自定義實現過異常嗎?怎麼寫的?

  很顯然,咱們絕大多數都寫過自定義或者業務異常,像AccountNotFoundException。在面試過程當中詢問這個Java異常問題的主要緣由是去發現你如何使用這個特性的。這能夠更準確和精緻的去處理異常,固然這也跟你選擇checked 仍是unchecked exception息息相關。經過爲每個特定的狀況建立一個特定的異常,你就爲調用者更好的處理異常提供了更好的選擇。相比通用異常(general exception),我更傾向更爲精確的異常。大量的建立自定義異常會增長項目class的個數,所以,在自定義異常和通用異常之間維持一個平衡是成功的關鍵。

9) JDK7中對異常處理作了什麼改變?

  這是最近新出的Java異常處理的面試題。JDK7中對錯誤(Error)和異常(Exception)處理主要新增長了2個特性,一是在一個catch塊中能夠出來多個異常,就像原來用多個catch塊同樣。另外一個是自動化資源管理(ARM), 也稱爲try-with-resource塊。這2個特性均可以在處理異常時減小代碼量,同時提升代碼的可讀性。對於這些特性瞭解,不只幫助開發者寫出更好的異常處理的代碼,也讓你在面試中顯的更突出。我推薦你們讀一下Java 7攻略,這樣能夠更深刻的瞭解這2個很是有用的特性。

10) 你遇到過 OutOfMemoryError 錯誤嘛?你是怎麼搞定的?

  這個面試題會在面試高級程序員的時候用,面試官想知道你是怎麼處理這個危險的OutOfMemoryError錯誤的。必須認可的是,無論你作什麼項目,你都會碰到這個問題。因此你要是說沒遇到過,面試官確定不會買帳。要是你對這個問題不熟悉,甚至就是沒碰到過,而你又有三、4年的Java經驗了,那麼準備好處理這個問題吧。在回答這個問題的同時,你也能夠藉機向面試秀一下你處理內存泄露、調優和調試方面的牛逼技能。我發現掌握這些技術的人都能給面試官留下深入的印象。

11) 若是執行finally代碼塊以前方法返回告終果,或者JVM退出了,finally塊中的代碼還會執行嗎?

  這個問題也能夠換個方式問:「若是在try或者finally的代碼塊中調用了System.exit(),結果會是怎樣」。瞭解finally塊是怎麼執行的,即便是try裏面已經使用了return返回結果的狀況,對了解Java的異常處理都很是有價值。只有在try裏面是有System.exit(0)來退出JVM的狀況下finally塊中的代碼纔不會執行。

12)Java中final,finalize,finally關鍵字的區別

  這是一個經典的Java面試題了。個人一個朋友爲Morgan Stanley招電信方面的核心Java開發人員的時候就問過這個問題。final和finally是Java的關鍵字,而finalize則是方法。final關鍵字在建立不可變的類的時候很是有用,只是聲明這個類是final的。而finalize()方法則是垃圾回收器在回收一個對象前調用,但也Java規範裏面沒有保證這個方法必定會被調用。finally關鍵字是惟一一個和這篇文章討論到的異常處理相關的關鍵字。在你的產品代碼中,在關閉鏈接和資源文件的是時候都必需要用到finally塊。

參考文章

https://www.xuebuyuan.com/324...
https://www.jianshu.com/p/49d...
http://c.biancheng.net/view/1...
https://blog.csdn.net/Lisilua...
https://blog.csdn.net/michael...

微信公衆號

Java技術江湖

若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站,做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源: 一些Java工程師經常使用學習資源,關注公衆號後,後臺回覆關鍵字 「Java」 便可免費無套路獲取。

個人公衆號

我的公衆號:黃小斜

做者是 985 碩士,螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,偶爾講點算法和計算機理論基礎,堅持學習和寫做,相信終身學習的力量!

程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取。

相關文章
相關標籤/搜索