歡迎你們關注個人公衆號:前程有光,金三銀四跳槽面試季,整理了1000多道將近500多頁pdf文檔的Java面試題資料,文章都會在裏面更新,整理的資料也會放在裏面### 前言
昨天在整理粉絲給我私信的時候,發現了一個挺有意思的事情。是這樣的,有一個粉絲朋友私信問我Java 的 Exception 和 Error 有什麼區別呢?說他在面試的時候被問到這個問題卡殼了,最後還好也是有驚無險的過了。在恭喜這位粉絲的同時,咱們再回過頭來這個問題,其實在面試中這是個常見的連環問題了,大多數面試官都喜歡用這個話題發問。當時看完當時內心也就下了個決心,必定要寫篇文章把 Java 的異常相關講明白,讓你們看完以後再遇到相似問題就會有所準備!java
有點 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
handler
,最終會致使線程退出。好了,到這裏咱們已經搞明白 throw 到底幹了些啥。可是咱們注意到, athrow 指令尋找的是一個 java.lang.Throwable 的子類對象的引用,也就是說 throw 語句後面只能跟 java.lang.Throwable 的子類對象,不然會編譯失敗。那麼Throwable 究竟是個什麼東西呢?this
Throwable
顧名思義,就是能夠被 throw 的對象啦!它是 java 中全部異常的父類:.net
public class Throwable implements Serializable { ... }
JDK 自帶的異常類簇,繼承關係大概是這個樣子的:
首先能夠看到 Throwable 分紅兩個大類,Exception 和 Error
Error 是 java 虛擬機拋出的錯誤,程序運行中出現的較嚴重的問題。
例如,虛擬機的堆內存不夠用了,就會拋出 OutOfMemoryError。這些異經常使用戶的代碼無需捕獲,由於捕獲了也沒用。
這就比如船壞了,而船上的乘客即使知道船壞了也沒辦法,由於這不是他們能解決的問題。
Exception 是應用程序中可能的可預測、可恢復問題。
啥意思呢?也就是說Exception都是用戶代碼層面拋出的異常。換句話說,這些異常都是船上的乘客本身能夠解決的。例如常見的空指針異常NullPointerException
,取數組下標越界時會拋出ArrayIndexOutOfBoundException
。
這些都是「乘客」的錯誤操做引起的問題,因此「乘客」是能夠解決的。
到了這裏,粉絲問到的那道面試題,是否是就已經解決了呢?
我前面也說了,這是個常見的連環問題。那麼解決了第一個問題,面試官接下來會問什麼呢?
經過上面一節的敘述你們能夠看到,就 Throwable
體系自己,與程序員關係比較大的其實仍是 Exception
及其子類。由於船上的乘客都是程序員們創造,因此他們的錯誤行爲,程序員仍是要掌握得比較透徹的。
Exception
可分爲兩種,CheckedException
和 UncheckedException
。
顧名思義,UncheckedException 也就是能夠不被檢查的異常。JVM 規定繼承自 RuntimeException 的異常都是 UncheckedException.
全部非 RuntimeException 的 Exception.
那麼問題來了,什麼叫 被檢查的異常 ? 誰檢查?
試想一下如下這個開發場景:
爲了解決這個場景中出現的問題,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 < -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
不是一個功能,而是一套讓異常捕獲語句更加優雅的解決方案。
對於任何實現了 java.io.Closeable
接口的類,只要在 try
後面的()中初始化,JVM
都會自動增長 finally 代碼塊去執行這些 Closeable
的 close()
方法。
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; }
因爲 FileReader
和 BufferedReader
都實現了 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面試題資料,文章都會在裏面更新,整理的資料也會放在裏面