異常:Core Java 7.1

異常:程序運行過程中,由於程序本身的錯誤或者外部環境的影響,而出現的異常情況

斷言:爲確保程序能正常運行或者排查程序出現異常的原因,需要編寫一些測試代碼進行測試。但程序正式運行時,是不允許測試代碼工作的。如果直接刪掉這些測試代碼,再次遇到異常時,可能又需要重複編寫相同的測試代碼。可以通過斷言來避免這些問題。

日誌:當程序遇到異常時,並不能總是能同用戶或者終端溝通。此時可以通過日誌記錄下問題,以備日後進行分析。

異常處理

當程序出現異常時,比如網絡中斷、文件格式錯誤、數組下標越界、null對象,如果程序正在進行某些操作而因爲出現異常無法完成,程序應該能:

  1. 返回到安全狀態,並讓用戶能執行其他操作
  2. 允許用戶保存所有操作的結果,並以妥善的方式終止程序

異常處理的任務就是 將控制權 從異常產生的代碼處 轉移到 能夠處理這種異常的處理器(ExceptionHandler)處

如果方法不能夠以正常的途徑完成它的任務,那就通過另外一個路徑退出方法。這種情況下,方法不返回任何值,而是拋出一個封裝了錯誤信息的對象,並立即退出。這種情況下,調用這個方法的代碼也無法繼續執行,而是異常處理機制開始搜索能夠處理這種異常狀況的異常處理器(ExceptionHandler)。

幾種類型的錯誤

用戶輸入錯誤、硬件錯誤(打印機關閉的、打印機打印過程中沒有紙了)、物理限制(內存、硬盤滿了,空間不足了)
代碼錯誤

Throwable -> [ Error, Exception -> [ IOException, RuntimeException ] ]

Error是JVM自身錯誤,即JVM內部錯誤或者資源耗盡錯誤,應用程序不應該拋出這種類型的對象。

應用程序的錯誤(BUG)會拋出RuntimeException,常見的有:

  • ClassCastException
  • ArrayIndexOutOfBoundsException
  • nullPointer

應用程序本身沒有問題,而是IO等其它問題,會拋出IOException,常見的有:

  • 文件指針已經到文件尾部:EOFException
  • 試圖打開一個不存在的文件:FileNotExistsException,儘管可以在打開之前先檢查一下,但是有可能下一瞬間,它又被其它程序刪除了。
  • 根據給定的字符串找不到類:ClassNotFoundException

聲明受檢異常

在Exception中,RuntimeException是非受檢異常(unchecked),其它是可受檢異常(checked Exception)。

所有可能的受檢異常都必須在方法中聲明。

不需要聲明JVM的內部錯誤,即Error,因爲根本不可控。
也不需要聲明RuntimeException,因爲它是程序錯誤(BUG)引起的(而不是環境錯誤),是應該主動避免的。

聲明、拋出、傳遞、捕獲、處理

傳遞、拋出、聲明

當代碼可能出現受檢異常時,最好的選擇是什麼也不做,而是將異常傳遞給調用者。傳遞的方式就是拋出異常,需要在
如果 read 方法出現了錯誤, 就 讓 read 方法的調用者去操心!即聲明read方法可能會拋出一個 IOException

捕獲、處理

應該捕獲那些知道如何處理的異常, 而將那些不知道怎樣處理的異常繼續進行傳遞。

仔細閱讀一下 Java API 文檔, 以便知道每個方法可能會拋出哪種異常, 然後再決定是自己處理,還是添加到 throws 列表中。

再次拋出異常與異常鏈

當超類的方法沒有拋出異常,而子類重寫後的方法代碼可能會拋出受檢異常時,需要在子類方法中自行捕獲並處理。因爲規則是子類拋出的異常不能大於父類。

這時就可以在catch語句中,將異常再次包裝成一個非受檢異常,並直接拋出它。

try{
}
catch(SQLException e){
	Throwable throwable = new ServletException("database error");
	throwable.initCause(e);
	throw e
}

當上層調用方捕獲到以上再次包裝後的異常時,就可以用如下方式獲取原始異常SQLException e:

throwable.getCause();

強烈建議使用這種包裝技術,如果開發了一個供其他程序員使用的子系統,這樣就可以用戶拋出子系統中的高級異常,而不會丟失原始異常的細節。

強烈建議耦合try/catch 和 try/finally 語句塊,這樣可以提高代碼的清晰度。方式如下:

try{
	try{
		// 工作代碼
	}
	finally{
		//  釋放資源
	}
}
catch(){
	// 處理、或者拋出異常
}

這種方式不僅清楚,而且還有一個功能,就是會報告finally子句中出現的錯誤

帶資源的try語句(try-with-resource)

如果在try塊中打開了資源,並且這些資源實現了AutoCloseable接口,如InputStream\OutputStream\Reader\Writer等及其子類,那麼就可以簡寫如下(try-with-resource):

public static void main(String[] args){

		try(
				Scanner in = new Scanner(new FileInputStream("IntegerProxy2.java"));
				PrintWriter out = new PrintWriter("out.txt")
			) // 在try關鍵字後加上一隊小括號(),所有的要打開的實現了AutoCloseable的資源,都必須定義在這對小括號內,
			 // 這樣,javac就知道finally要釋放哪些資源了。
			{
				while(in.hasNextLine()){
					out.println(in.nextLine());
				}
		} catch(FileNotFoundException e){
			e.printStackTrace();
		}
    }

注意,只有實現了AutoCloseable的類型的變量纔可以定義在try()的小括號內,如果在try()中定義一個String name="lily",編譯無法通過。

異常處理注意

  • 不要用異常處理代替測試,因爲處理異常花費的時間遠超正常的測試花費的時間
  • 不要每條語句一個try-catch,而是把所有catch語句連在一起寫,比較清晰
  • 儘量將異常層次化,而不是所有的異常全都catch封裝到一個Throwable或者Exception對象中去
  • 將一種異常轉換成另一種更加合適的異常時不要猶豫。例如在解析某個文件的一個整數時,捕獲到了NumberFormatException異常,然後將它轉換成IOException或者MySubsystemException的子類。
  • **如果要調用的方法拋出的異常發生的可能性概率極低,可以用捕獲後不做任何處理的方式直接關閉它。**但是不應該什麼也不做,不應該既不捕獲處理、也不拋出。
  • 噹噹前方法的參數或者返回值爲null時,應該根據實際業務選擇是否拋出異常,在當前方法中拋出一個有具體明確的說明信息的Exception,比調用當前方法的方法拋出一個無明確信息的NullPointerException這樣一個運行時異常RuntimeException更有用(有時可能需要根據業務的實際情況處理,有時業務允許結果爲空)
  • 底層的方法更應該傳遞異常,而不是捕獲和處理異常。這樣就讓高層次的方法通知用戶發生了錯誤、或者放棄不合適的命令
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章