異常 Exception 細節 面試題總結 MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

Exception 面試題總結

多線程 try catch 異常捕獲問題

一道面試題

問:在 try catch 中開啓新的線程,能捕獲線程裏面的異常嗎?java

例如:git

try {
    new Thread(() -> System.out.println(1 / 0)).start(); //Runnable的run()方法拋出了 unchecked exception
    //new Thread(() -> throw new RuntimeException("拋出了 unchecked exception")).start();
} catch (Exception e) {
    e.printStackTrace();
    System.out.println("這裏能執行到嗎?"); //不能夠
}

上面是捕獲不到異常的,而若是改成下面這種形式,則能夠捕獲到異常:github

new Thread(() -> {
    try {
        System.out.println(1 / 0); //Runnable的run()方法並無拋出異常,而是本身捕獲了異常
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("這裏能執行到嗎?"); //能夠
    }
}).start();

其實使用 try catch 捕獲異常時有一個規範,那就是儘可能用 try catch 包住最少的代碼,有些同窗一上來就用 try catch 把整個方法的邏輯包住,這樣很是不合適,好比就會致使上述 try catch 失效。面試

結論

在java多線程程序中,全部線程都不容許拋出checked exception,也就是說各個線程的checked exception必須由本身捕獲。這一點是經過java.lang.Runnable.run()方法聲明進行的約束,由於此方法聲明上沒有throws部分。微信

可是線程依然有可能拋出一些運行時的異常(即unchecked exception,RuntimeException),當此類異常跑拋出時,此線程就會終結,而對於其餘線程徹底不受影響,且徹底感知不到某個線程拋出的異常。多線程

JVM的這種設計源自於這樣一種理念:線程是獨立執行的代碼片段,線程的問題應該由線程本身來解決,而不要委託到外部。less

在Java中,線程方法的異常(不管是checked仍是unchecked exception),都應該在線程代碼邊界以內(run方法內)進行try catch並處理掉。換句話說,咱們不能捕獲從線程中逃逸的異常。dom

JVM 處理機制

查看 Thread 的源碼能夠幫忙分析當線程出現未捕獲異常時的處理邏輯。測試

首先看Thread.dispatchUncaughtException()方法:this

//Dispatch an uncaught exception to the handler. This method is intended to be called only by the JVM.
private void dispatchUncaughtException(Throwable e) {
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

這個方法僅僅被 JVM 調用,用來將 uncaught exception 分發到 handler 去處理,這個 handler 是哪來的呢?

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?  uncaughtExceptionHandler : group;
}

Returns the handler invoked when this thread abruptly terminates due to an uncaught exception.

返回此線程因爲未捕獲的異常而忽然終止時調用的handler。

If this thread has not had an uncaught exception handler explicitly set then this thread's ThreadGroup object is returned, unless this thread has terminated, in which case null is returned.

若是此線程沒有顯式設置未捕獲的異常 handler,則返回此線程的 ThreadGroup 對象,除非此線程已終止,在這種狀況下返回 null。

這裏的uncaughtExceptionHandler只有一個地方初始化:

private volatile UncaughtExceptionHandler uncaughtExceptionHandler;// null unless explicitly set

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    checkAccess();
    uncaughtExceptionHandler = eh;
}

若是返回的是 ThreadGroup 的話,默認會一直找到頂層 ThreadGroup(相似雙親委派模型),而後會找 Thread 類共用的 defaultUncaughtExceptionHandler,若是存在則調用,若是不存在,則打印線程名字和異常:

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) { //不是ThreadDeath
            System.err.print("Exception in thread \""  + t.getName() + "\" "); //打印線程名字
            e.printStackTrace(System.err); //打印異常
        }
    }
}

defaultUncaughtExceptionHandler也只有一個地方初始化:

private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;// null unless explicitly set

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    //檢查權限
    defaultUncaughtExceptionHandler = eh;
}

也就是說,當一個線程中有未捕獲的異常時,JVM 會經過調用 UncaughtExceptionHandler 的 uncaughtException 方法處理異常,若是沒有設置,則會直接打印線程名字和異常。

finally 語句的執行與 return 的關係

finally 語句是否是必定會被執行

問:Java異常捕獲機制try...catch...finally塊中的finally語句是否是必定會被執行?

答:不必定,至少有兩種狀況下finally語句是不會被執行的:

  • try語句沒有被執行到,如在try語句以前就返回了,這樣finally語句就不會執行,這也說明了finally語句被執行的必要而非充分條件是:相應的try語句必定被執行到。
  • 在try塊中有System.exit(0)這樣的語句,System.exit(0)是終止Java虛擬機JVM的,連JVM都中止了,全部都結束了,固然finally語句也不會被執行到。

正常狀況下的執行順序

finally語句是在try的return語句執行以後,return返回以前執行的

測試案例:

System.out.println(test());

public String test() {
    try {
        System.out.println("try block");
        if (new Random().nextBoolean())  return "直接返回";
        else return test2();
    } finally {
        System.out.println("finally block");
    }
}

public String test2() {
    System.out.println("return statement"); //return語句執行以後才執行finally語句
    return "調用方法返回";
}

運行結果:

try block
finally block
直接返回

try block
return statement
finally block
調用方法返回

說明try中的return語句先執行了,但並無當即返回,而是等到finally執行結束後再返回

這裏你們可能會想:若是finally裏也有return語句,那麼是否是就直接返回了,try中的return就不能返回了?看下面。

finally裏也有return語句

finally塊中的return語句會覆蓋try塊中的return返回

System.out.println(test());

public static String test() {
    try {
        System.out.println("try block");
        return "在try中返回";
    } finally {
        System.out.println("finally block");
        return "在finally中返回";
    }
    // return "finally外面的return就變成不可到達語句,須要註釋掉不然編譯器報錯";
}

運行結果:

try block
finally block
在finally中返回

這說明finally裏的return直接返回了,就無論try中是否還有返回語句。

這裏還有個小細節須要注意,finally里加上return事後,finally外面的return b就變成不可到達語句了,也就是永遠不能被執行到,因此須要註釋掉不然編譯器報錯。

finally裏修改返回值

若是finally語句中沒有return語句覆蓋返回值,那麼原來的返回值可能由於finally裏的修改而改變,也可能不變

測試用例:

System.out.println(test());

public static int test() {
    int b = 20;
    try {
        System.out.println("try block");
        return b += 80;
    } finally {
        b += 10;
        System.out.println("finally block");
    }
}

運行結果:

try block
finally block
100     //這是關鍵

測試用例2:

System.out.println(test());

public static List<Integer> test() {
    List<Integer> list = new ArrayList<Integer>();
    list.add(10086);
    try {
        System.out.println("try block");
        return list;
    } finally {
        list.add(10088);
        System.out.println("finally block");
    }
}

運行結果:

try block
finally block
[10086, 10088]     //這是關鍵

這其實就是Java究竟是傳值仍是傳址的問題了,簡單來講就是:Java中只有傳值沒有傳址

這裏你們可能又要想:是否是每次返回的必定是try中的return語句呢?那麼finally外的return不是一點做用沒嗎?請看下面

try中return以前拋異常

try塊裏拋異常的return語句在異常的狀況下不會被執行,這樣具體返回哪一個看狀況

public class TestFinally {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test() {
        int b = 0;
        try {
            System.out.println("try block");
            b = b / 0;
            return b += 1;
        } catch (Exception e) {
            b += 10;
            System.out.println("catch block");
        } finally {
            b += 100;
            System.out.println("finally block");
        }
        return b;
    }
}

運行結果是:

try block
catch block
finally block
110

這裏因 爲在return以前發生了異常,因此try中的return不會被執行到,而是接着執行捕獲異常的 catch 語句和最終的 finally 語句,此時二者對b的修改都影響了最終的返回值,這時最後的 return b 就起到做用了。

這裏你們可能又有疑問:若是catch中有return語句呢?固然只有在異常的狀況下才有可能會執行,那麼是在 finally 以前就返回嗎?看下面。

catch中有return語句

當發生異常後,catch中的return執行狀況與未發生異常時try中return的執行狀況徹底同樣

public class TestFinally {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test() {
        int b = 0;
        try {
            System.out.println("try block");
            b = b / 0;
            return b += 1;
        } catch (Exception e) {
            b += 10;
            System.out.println("catch block");
            return 10086;
        } finally {
            b += 100;
            System.out.println("finally block");
        }
        //return b;
    }
}

運行結果:

try block
catch block
finally block
10086

說明了發生異常後,catch中的return語句先執行,肯定了返回值後再去執行finally塊,執行完了catch再返回,也就是說狀況與try中的return語句執行徹底同樣。

總結

  • finally塊的語句在try或catch中的return語句執行以後返回以前執行
  • 且finally裏的修改語句可能影響也可能不影響try或catch中return已經肯定的返回值
  • 若finally裏也有return語句則覆蓋try或catch中的return語句直接返回

Checked 異常和 Unchecked 異常

Java包含兩種異常:checked異常和unchecked異常:

  • Checked異常繼承java.lang.Exception類,Checked異常必須經過try-catch被顯式地捕獲或者經過throws子句進行傳遞。
  • Unchecked異常即運行時異常,繼承自java.lang.RuntimeException類,是那些可能在 Java 虛擬機正常運行期間拋出的異常,unchecked異常能夠即沒必要捕獲也不拋出。

運行時異常
運行時異常咱們通常不處理,當出現這類異常的時候程序會由虛擬機接管。好比,咱們歷來沒有去處理過NullPointerException,並且這個異常仍是最多見的異常之一。

出現運行時異常的時候,程序會將異常一直向上拋,一直拋到遇處處理代碼,若是沒有catch塊進行處理,到了最上層,若是是多線程就有Thread.run()拋出,若是不是多線程那麼就由main.run()拋出。拋出以後,若是是線程,那麼該線程也就終止了,若是是主程序,那麼該程序也就終止了。

其實運行時異常的也是繼承自Exception,也能夠用catch塊對其處理,只是咱們通常不處理罷了,也就是說,若是不對運行時異常進行catch處理,那麼結果不是線程退出就是主程序終止。若是不想終止,那麼咱們就必須捕獲全部可能出現的運行時異常。

兩種異常的使用場景

  • Checked和unchecked異常從功能的角度來說是等價的,能夠用checked異常實現的功能必然也能夠用unchecked異常實現,反之亦然。
  • 選擇checked異常仍是unchecked異常是我的習慣或者組織規定問題。並不存在誰比誰強大的問題。
  • Unchecked異常避免了沒必要要的try-catch塊,不會使代碼顯得雜亂;Unchecked異常不會由於異常聲明彙集使方法聲明顯得雜亂。

checked 異常使用案例

public class Test {
    public static void main(String[] args) {

        try {
            new Test().testException();
        } catch (MyException e) {
            e.printStackTrace();
            System.out.println("調用拋出checked異常的方法時,一樣必須經過try-catch顯式地捕獲或者經過throws子句進行傳遞");
        }
    }

    void testException() throws MyException {
        throw new MyException("拋出checked異常時,必須經過try-catch顯式地捕獲或者經過throws子句進行傳遞");
    }

    class MyException extends Exception {
        MyException(String s) {
            super(s);
        }
    }
}

unchecked 異常使用案例

和上面相比,只需把自定義的異常由繼承自Exception改成繼承自RuntimeException便可。

因爲RuntimeException繼承自Exception,因此修改後上面其餘代碼都不須要改變就能夠正常使用。但RuntimeException能夠即沒必要捕獲也不拋出:

public class Test {
    public static void main(String[] args) {
        new Test().testException();
    }

    void testException() {
        throw new MyException("unchecked異常能夠即沒必要捕獲也不拋出");
    }

    class MyException extends RuntimeException {
        MyException(String s) {
            super(s);
        }
    }
}

checked 異常錯誤使用案例

public class Test {

    public static void main(String[] args) {
        try {
            start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void start() {
        System.out.println("這個方法並無聲明會拋出checked 異常,例如IOException");
    }
}

上面的代碼編譯是通不過的:

由於IOException是checked異常,而start方法並無拋出IOException,編譯器將在處理IOException時報錯。

可是若是你將IOException改成Exception,編譯器報錯將消失,由於Exception能夠用來捕捉全部運行時異常(包括unchecked異常),這樣就不須要聲明拋出語句。

將上例中的 IOException 改成 unchecked 異常也是能夠的,例如改成 NullPointerException

其餘小知識點

error和exception有什麼區別

  • error表示系統級的錯誤,是java運行環境內部錯誤或者硬件問題,不能期望程序來處理這樣的問題,除了退出運行外別無選擇,它是Java虛擬機拋出的。
  • exception 表示程序須要捕捉、須要處理的異常,是因爲程序設計的不完善而出現的問題,程序必須處理的問題。

final、finally、finalize的區別

  • final用於聲明變量、方法和類的,分別表示變量值不可變,方法不可覆蓋,類不能夠繼承
  • finally是異常處理中的一個關鍵字,表示finally{}裏面的代碼必定要執行
  • finalize是Object類的一個方法,在垃圾回收的時候會調用被回收對象的此方法。

常見的Exception和Error有哪些

  • 常見的 Checked 異常:ClassNotFoundExceptionCloneNotSupportedException,DataFormatException,IllegalAccessException,InterruptedExceptionIOExceptionNoSuchFieldExceptionNoSuchMethodException,ParseException,TimeoutException,XMLParseException
  • 常見的 Unchecked 異常:BufferOverflowException,ClassCastExceptionIllegalArgumentException,IllegalStateException,IndexOutOfBoundsException,NoSuchElementException,NullPointerException,SecurityException,SystemException,UnsupportedOperationException
  • 常見的 Error:OutOfMemoryErrorStackOverflowError、NoClassDefFoundError、UnsatisfiedLinkError、IOErrorThreadDeath、ClassFormatError、InternalError、UnknownError

2019-4-26

相關文章
相關標籤/搜索