1. 程式人生 > >Exception 和Error的相同點和區別

Exception 和Error的相同點和區別

相同點

Exception和Error都是繼承了Throwable類,在java中只有Throwable型別的例項才可以被丟擲(throw)或者捕獲(catch),他是異常處理機制的基本組成型別。

不同點

Exception是在程式正常執行中,可以預料到的意外情況,應該捕獲並進行異常處理。
Exception分為可檢查異常(checked)和不檢查異常(unchecked)

  • 可檢查異常在原始碼裡面需要try,catch,這是編譯時檢查的一部分。常見的Checked Exception有IOException
  • 不可檢查異常就是執行時異常,例如NullPointerException,ArrayIndexOfBoundsException,ClassCastException,SecurityException,通常是因為程式碼有邏輯錯誤。

error是正常情況下基本不會出現的情況,會對程式執行造成威脅,error不需要捕獲,比如OutOfMemoryError,它就是Error的子類。

  • LinkageError:常見的LinkageError有NoClassDefFoundError,UnsatisfiedLinkError,ExceptionInInitializerError。
  • VirtualMachineError:另外還有另一類Error是VirtualMachineError,他下屬的有些Error也挺常見,比如OutOfMemoryError,StackOverflowError。

NoClassDefFoundError和ClassNotFoundException有什麼區別?

  1. ClassNotFoundException的產生原因:
    • java支援使用class.forName方法動態載入類,如果一個類的類名作為引數傳遞給這個方法,這個類就會被載入到JVM記憶體中,如果這個類在類路徑中沒有找到就會在執行時丟擲ClassNotFoundException異常。解決辦法就是把類和他所依賴的包都存放在類路徑中。除了Class.forName,ClassLoader.loadClass、ClassLoader.findSystemClass在動態載入類到記憶體中的時候也可能會丟擲這個異常。
    • 另一個導致ClassNotFoundException的原因就是:當一個類已經某個類載入器載入到記憶體中了,此時另一個類載入器又嘗試著動態地從同一個包中載入這個類。
    • 總結:載入時從外儲存器找不到需要的class就出ClassNotFoundException
  2. NoClassDefFoundError產生的原因
    • JVM或者ClassLoader嘗試載入(可以通過正常的方法呼叫,也可能是使用new來建立新的物件)類的時候卻找不到類的定義。要查詢的類在編譯的時候是存在的,執行的時候卻找不到了。這個錯誤往往是你使用new操作符來建立一個新的物件但卻找不到該物件對應的類。這個Error不應該寫程式碼去捕獲,他是由JVM引起的。NoClassDefFoundError: 當目前執行的類已經編譯,但是找不到它的定義時解決辦法:找到那些在開發期間存在於類路徑下但在執行期間卻不在類路徑下的類

實踐中的異常處理

異常處理需要我們寫捕獲程式碼或者在finally裡面做一些資源回收的工作。可以考慮使用一些java好用的特性。比如try-with-resources和multiple catch,在編譯時期,自動生成相應的處理邏輯,比如關閉哪些擴充套件了AutoCloseable的物件或者是擴充套件了Closeable的物件。

try(BufferedReader br = new BufferedReader(...);
	BufferWriter bw = new BufferedWriter(...)) {  //Try-with-resources
		//do something
	} catch (IOException | XException e) { //Multiple-catch
		//handle it
	}

下面舉一個不正確的異常處理例子

try {
	// 業務程式碼
	// …
	Thread.sleep(1000L);
} catch (Exception e) {
	// Ignore it
}

上面的程式碼違反了異常處理的兩個基本原則

  • 一,儘量不要類似Exception這樣的通用異常,而是應該捕獲特定的異常,例如Thread.sleep()丟擲的InterrruptedException。這樣便於理解這一段程式碼的目的。
  • 二,不能假設某一段程式碼不會發生,要把異常丟擲或輸出到日誌(Logger)中,如果程式出問題,便於查詢分析問題。

程式碼二

try {
	// 業務程式碼
	// …
} catch (IOException e) {
	e.printStackTrace();
}

這段程式碼作為實驗程式碼可以,但是在產品程式碼中不允許,因為printStackTrace的輸出選項是標準出錯,難以判斷到底輸出到哪個地方去了。尤其是對於分散式系統,發生異常的時候,沒法找到堆疊軌跡stacktrace,很難診斷問題,所以最好輸出到日誌系統中去。

程式碼三

public void readPreferences(String fileName){
	//...perform operations...
	InputStream in = new FileInputStream(fileName);
	//...read the preferences file...
}

如果fileName是null,程式就會丟擲NullPointerException,但是由於沒有第一時間暴露出問題,堆疊資訊可能非常令人費解,往往需要相對複雜的定位。可以修改一下,讓問題“throw early”

public void readPreferences(String filename) {
	Objects. requireNonNull(filename);
	//...perform other operations...
	InputStream in = new FileInputStream(filename);
	//...read the preferences file...
}

止於“catch late”,如果捕獲異常之後不知道如何處理,就保留原來的異常資訊,直接再次丟擲異常,到了更高的層面,懂得了更多的業務邏輯,往往更清楚怎麼處理。