面試官:小夥子,你給我說一下Java Exception 和 Error 的區別吧?

歡迎你們關注個人公衆號:前程有光,金三銀四跳槽面試季,整理了1000多道將近500多頁pdf文檔的Java面試題資料,文章都會在裏面更新,整理的資料也會放在裏面### 前言
昨天在整理粉絲給我私信的時候,發現了一個挺有意思的事情。是這樣的,有一個粉絲朋友私信問我Java 的 Exception 和 Error 有什麼區別呢?說他在面試的時候被問到這個問題卡殼了,最後還好也是有驚無險的過了。在恭喜這位粉絲的同時,咱們再回過頭來這個問題,其實在面試中這是個常見的連環問題了,大多數面試官都喜歡用這個話題發問。當時看完當時內心也就下了個決心,必定要寫篇文章把 Java 的異常相關講明白,讓你們看完以後再遇到相似問題就會有所準備!java

throw 語句

有點 java 基礎的同窗應該都知道 throw 這個語句吧。咱們都知道throw 語句起到的做用,它會拋出一個 throwable 的子類對象,虛擬機會對這個對象進行一系列的操做,要麼能夠處理這個異常(被 catch),或者不能處理,最終會致使語句所在的線程中止。程序員

那麼 JVM 究竟是怎麼作的呢?讓咱們一塊兒試試看吧:面試

首先寫一段代碼,throw 一個 RuntimeException:數組

package com.company;

public class TestException {
    public static void main(String[] args) {
        throw new RuntimeException();
    }
}

編譯後,到 class 文件所在目錄,用javap -verbose 打開 .class 文件:ide

javap -verbose TestException

能夠看到一些字節碼。咱們找到 TestException.main 函數對應的字節碼:函數

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class java/lang/RuntimeException
         3: dup
         4: invokespecial #3                  // Method java/lang/RuntimeException."<init>":()V
         7: athrow
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  args   [Ljava/lang/String;

code 部分,實際上前三行就是對應 new RuntimeExcpetion(),因爲主題緣由,這裏就不展開了。重頭戲是後面這個 athrow,它到底作了什麼呢?工具

1\. 先檢查棧頂元素,必須是一個java.lang.Throwable的子類對象的引用;

2\. 上述引用出棧,搜索本方法的異常表,是否存在處理此異常的 handler;

    2.1 若是找到對應的handler,則用這個handler處理異常;

    2.2 若是找不到對應的handler,當前方法棧幀出棧(退出當前方法),到調用該方法的方法中搜索異常表(重複2);

3\. 若是一直找不到 handler,當前線程終止退出,並輸出異常信息和堆棧信息(其實也就是不斷尋找 handler 的過程)。

能夠看到 throw 這個動做會形成幾個可能的反作用:ui

  1. 終止當前方法調用,並傳遞異常信息給方法調用方;
  2. 若是異常一直沒法在方法的異常表裏找到 handler,最終會致使線程退出。

好了,到這裏咱們已經搞明白 throw 到底幹了些啥。可是咱們注意到, athrow 指令尋找的是一個 java.lang.Throwable 的子類對象的引用,也就是說 throw 語句後面只能跟 java.lang.Throwable 的子類對象,不然會編譯失敗。那麼Throwable 究竟是個什麼東西呢?this

Throwable 類簇

Throwable 顧名思義,就是能夠被 throw 的對象啦!它是 java 中全部異常的父類:.net

public class Throwable implements Serializable {
    ...
}

JDK 自帶的異常類簇,繼承關係大概是這個樣子的:

面試官:小夥子,你給我說一下Java Exception 和 Error 的區別吧?

首先能夠看到 Throwable 分紅兩個大類,Exception 和 Error

Error

Error 是 java 虛擬機拋出的錯誤,程序運行中出現的較嚴重的問題。

例如,虛擬機的堆內存不夠用了,就會拋出 OutOfMemoryError。這些異經常使用戶的代碼無需捕獲,由於捕獲了也沒用。

這就比如船壞了,而船上的乘客即使知道船壞了也沒辦法,由於這不是他們能解決的問題。

Exception

Exception 是應用程序中可能的可預測、可恢復問題。

啥意思呢?也就是說Exception都是用戶代碼層面拋出的異常。換句話說,這些異常都是船上的乘客本身能夠解決的。例如常見的空指針異常NullPointerException,取數組下標越界時會拋出ArrayIndexOutOfBoundException

這些都是「乘客」的錯誤操做引起的問題,因此「乘客」是能夠解決的。

到了這裏,粉絲問到的那道面試題,是否是就已經解決了呢?

CheckedException 和 UncheckedException

我前面也說了,這是個常見的連環問題。那麼解決了第一個問題,面試官接下來會問什麼呢?

經過上面一節的敘述你們能夠看到,就 Throwable 體系自己,與程序員關係比較大的其實仍是 Exception 及其子類。由於船上的乘客都是程序員們創造,因此他們的錯誤行爲,程序員仍是要掌握得比較透徹的。

Exception 可分爲兩種,CheckedExceptionUncheckedException

  • UncheckedException

顧名思義,UncheckedException 也就是能夠不被檢查的異常。JVM 規定繼承自 RuntimeException 的異常都是 UncheckedException.

  • CheckedException

全部非 RuntimeException 的 Exception.

那麼問題來了,什麼叫 被檢查的異常 ? 誰檢查?

throws 語句

試想一下如下這個開發場景:

  • 同窗 A 寫了一個工具類,編譯後打成了一個 jar 包給同窗 B 使用
  • 同窗 B 調用這個工具類的時候,因爲應用場景不一樣,被拋出了不少不一樣類型的異常,每出現一種新的異常,同窗 B 都要修改代碼去適配,很是痛苦。。。

爲了解決這個場景中出現的問題,JVM 規定,每一個函數必須對本身要拋出的異常心中有數,在函數聲明時經過 throws 語句將該函數可能會拋出的異常聲明出來:

public Remote lookup(String name)
        throws RemoteException, NotBoundException, AccessException;

這個聲明就是前面說的被檢查的異常

那麼能夠不被檢查的異常又是咋回事呢?

其實 CheckedExcpetion 之因此要被 Check,主要仍是由於調用方是有呢你處理這些異常的。

java.net.URL 這個類的構造函數爲例:

public final class URL implements java.io.Serializable {
    ...
    public URL(String protocol, String host, int port, String file,
               URLStreamHandler handler) throws MalformedURLException {
       ...
       if (port < -1) {
                throw new MalformedURLException("Invalid port number :" +
                                                    port);
            }
    }
}

MalformedURLException就是一種checked exception. 當輸入的 port &lt; -1 時,程序就會拋出 MalformedURLException 異常,這樣調用方就能夠修正port輸入,獲得正確的URL了。

可是有一些狀況,好比下面這個函數:

public void method(){
   int [] numbers = { 1, 2, 3 };
   int sum = numbers[0] + numbers[3];
}

因爲 numbers 數組只有3個元素,但函數中卻取了第4個元素,因此調用 method() 時會拋出異常 ArrayIndexOutOfBoundsException。可是這個異常調用方是沒法修正的。

對於這種狀況,JVM 特地規定了 RuntimeException 及其子類的這種 UnchekcedException,能夠不被 throws 語句聲明,編譯時不會報錯。

異常處理

上面咱們介紹了異常的定義和拋出方式,那麼怎麼捕獲並處理異常呢?這個時候就輪到 try catch finally 出場了。舉個例子:

public void readFile(String filePath) throws FileNotFoundException {
    FileReader fr = null;
    BufferedReader br = null;
    try{
        fr = new FileReader(filePath);
        br = new BufferedReader(fr);
        String s = "";
        while((s = br.readLine()) != null){
            System.out.println(s);
        }
    } catch (IOException e) {
        System.out.println("讀取文件時出錯: " + e.getMessage());
    } finally {
        try {
            br.close();
            fr.close();
        } catch (IOException ex) {
            System.out.println("關閉文件時出錯: " + ex.getMessage());
        }
    }
}

這是一個逐行打印文件內容的函數。當輸入的 filePath 不存在時,會拋出 CheckedException FileNotFoundException

在文件讀取的過程當中,也會出現一些意外狀況可能形成一些 IOException,所以代碼對可能出現的 IOException 進行了 try catch finally 的處理。 try 代碼塊中是正常的業務代碼, catch 是對異常處理,finally 是不管try 是否異常,都要執行的代碼。對於 readFile 這個函數來講,就是要關閉文件句柄,防止內存泄漏。

這裏比較難受的是,因爲 fr br 須要在 finally 塊中執行,因此必需要在 try 前先聲明。有沒有優雅一點的寫法呢?

這裏要介紹一下 JDK 7 推出的新特性:

try-with-resources

try-with-resources 不是一個功能,而是一套讓異常捕獲語句更加優雅的解決方案。

對於任何實現了 java.io.Closeable 接口的類,只要在 try 後面的()中初始化,JVM 都會自動增長 finally 代碼塊去執行這些 Closeableclose()方法。

Closable 定義以下:

public interface Closeable extends AutoCloseable {

    /**
     * Closes this stream and releases any system resources associated
     * with it. If the stream is already closed then invoking this
     * method has no effect.
     *
     * <p> As noted in {@link AutoCloseable#close()}, cases where the
     * close may fail require careful attention. It is strongly advised
     * to relinquish the underlying resources and to internally
     * <em>mark</em> the {@code Closeable} as closed, prior to throwing
     * the {@code IOException}.
     *
     * @throws IOException if an I/O error occurs
     */
    public void close() throws IOException;
}

因爲 FileReaderBufferedReader 都實現了 Closeable 接口,因此上述前面咱們的 readFile 函數能夠改寫爲:

public void readFile(String filePath) throws FileNotFoundException {
        try(
                FileReader fr = new FileReader(filePath);
                BufferedReader br = new BufferedReader(fr)
        ){
            String s = "";
            while((s = br.readLine()) != null){
                System.out.println(s);
            }
        } catch (IOException e) {
            System.out.println("讀取文件時出錯: " + e.getMessage());
        }
    }

是否是清爽了許多呢?

最後

歡迎你們關注個人公衆號:前程有光,金三銀四跳槽面試季,整理了1000多道將近500多頁pdf文檔的Java面試題資料,文章都會在裏面更新,整理的資料也會放在裏面

相關文章
相關標籤/搜索