Java基礎—異常
一、概念
異常是程式中的一些錯誤,但並不是所有的錯誤都是異常,並且錯誤有時候是可以避免的。
異常體
Throwable:所有異常類的超類
Error:它表示不希望被程式捕獲或者是程式無法處理的錯誤
Exception:它表示使用者程式可能捕捉的異常情況或者說是程式可以處理的異常
其中異常類Exception
又分為執行時異常(RuntimeException
)和非執行時異常。
Java異常又可以分為不受檢查異常(Unchecked Exception
)和檢查異常(Checked Exception
)。
檢查異常屬於編譯時異常,也就是編譯時就會出現的異常,必須進行處理(try catch/throws)
而不受檢查的異常屬於執行時異常,在編譯期間不可查,在程式控制範圍之外。
java中常見異常請參見:http://blog.csdn.net/liu_jian140126/article/details/50517001
更形象更全面的版本:http://www.importnew.com/16725.html
二、異常處理機制
瞭解異常處理機制之前,先要了解異常情形(exception condition),它是指阻止當前方法或作用域繼續執行的問題。
所以,異常發生的時候,作用域後續的程式碼無法繼續執行!
丟擲異常後,會有幾件事隨之發生。首先,是像建立普通的java物件一樣將使用new
然後,當前的執行路徑(已經無法繼續下去了)被終止,並且從當前環境中彈出對異常物件的引用。此時,異常處理機制接管程式
更多異常機制的講解,參見(推薦!):http://www.cnblogs.com/Qian123/p/5715402.html
異常處理機制主要分為兩大類——捕獲異常、丟擲異常
1.捕獲異常
常用捕獲語句結構:
try { //可能發生異常的程式碼 } catch (Exception e1) { // 捕獲異常後的處理程式碼 } catch (Exception e2) {// 捕獲異常後的處理程式碼 } finally { // 總是會執行的程式碼(可選)
// 例如,釋放資源,資料庫連線、IO流、redis的連線等資源
}
注意點:
1.try語句塊中的是可能出現異常的語句;try中宣告的變數生命週期僅在try語句塊中有效,通常,我們將宣告寫在try之前以提高生命週期!
2.catch語句塊是捕獲異常後的異常處理;發生異常後catch語句依次檢查(catch語句捕獲的異常應當是逐級捕獲,先捕獲小的異常,後捕獲大的異常),當某個異常塊被捕獲後,其它語句塊便被旁路。
Exception常見的兩個方法: e.printStackTrace()——列印異常的堆疊 e.getMessage()——得到異常訊息(傳入的message屬性的資訊) 詳細的介紹可以參見API,並查閱相關的原始碼檢視
3.finally語句無論是否發生異常都會執行,如果之前程式碼有return語句,那麼會在return語句之前執行,通常,可以用來做一些資源關閉的操作!
捕獲的異常的執行順序可以參考以下的小例子:
public static void main(String[] args) { int i = 100; try { i = i + 10; System.out.println("異常發生前+10......" + i); i = 1 / 0; i = i + 10; System.out.println("異常發生後+10...." + i); } catch (Exception e) { i = i + 10; System.out.println("捕獲異常並+10......" + i); System.out.println(e.getMessage()); } finally { i = i + 10; System.out.println("finally語句塊執行+10..." + i); } System.out.println("異常處理後...." + i); }
之前我們說過,發生異常後,當前作用域程式碼無法執行!但是異常處理完畢後的程式碼可以正常執行(不然我們要異常處理幹嘛呢),但是隻有異常發生之前對變數的修改是有效的,異常發生後對變數的操作將不會執行!
上面的try_catch的模型,一般用於非執行時異常(checked Exception),執行時異常一般不需要手動進行處理
2.丟擲異常
在方法簽名上宣告丟擲的異常(自動丟擲一個異常物件):
對於異常情形,已經無法繼續下去了,因為在當前環境下無法獲得必要的資訊來解決問題,你所能做的就是從當前環境中跳出,並把問題提交給上一級環境,這就是丟擲異常時所發生的事情。
方法只是丟擲異常,誰呼叫誰負責處理(要麼繼續外拋,要麼try_catch進行捕獲處理)
//其中file()方法選擇了將異常丟擲,那麼在發生異常的時候就會丟擲一個異常的物件!呼叫它的main方法就必須處理:要麼繼續在方法宣告上丟擲,要麼進行try_catch
//如果方法中丟擲了具有父子關係的異常,那麼如果異常是統一處理的,可以只丟擲大的異常,捕獲大的異常,統一處理,想不同的異常區別處理,可以遵循catch塊的原則,分別處理!
手動丟擲一個異常:
看例項:
public static void main(String[] args) { inputNumber(0); } // 丟擲異常 public static void inputNumber(int num){ if (num == 1) { System.out.println("輸入了:1"); } else { // 手動丟擲異常 throw new RuntimeException("傳入的不是1!(只能傳入1!)"); } }
丟擲異常就是以上兩種方式:在方法宣告處丟擲一個異常的型別!——throws
在程式碼處手動丟擲一個異常物件!——throw
throw出的異常一般是執行時異常(RuntimeException及其子類等),如果丟擲了非執行時異常(例如直接丟擲Exception:throw new Exception(),那它可能不是一個執行時異常),還需要處理!
三、自定義異常
我們採用的是繼承異常類(Exception或者RuntimeException)的形式:
public class MyException extends RuntimeException{
但是異常類怎麼寫呢?我們既然也是定義的異常類,那麼我們可以看看它的父類怎麼寫的:
原來就是一個序列號再加幾個過載的構造器吖!
序列號是用於序列化的,構造器中的具體內容可以點選原始碼檢視到,像message其實就是Throwable中的 一個 屬性detilMessage,用於異常提示的。
那我們仿造它來一個:
public class MyException extends RuntimeException{ static final long serialVersionUID = -7034868990745766939L; public MyException() { } public MyException(String message) { super(message); } }
把上面丟擲的異常換成我們實現的自定義異常(RuntimeException的子類)
public static void main(String[] args) { inputNumber(0); } // 丟擲異常 public static void inputNumber(int num){ if (num == 1) { System.out.println("輸入了:1"); } else { // 手動丟擲異常 throw new MyException("來自自定義異常:傳遞的引數只能為:1!"); } }
當然,以上只是根據父類編寫一個基本的自定義異常,我們還可以自定義更加豐富的異常類(異常類也是一個普通的類)
public class MyException extends RuntimeException{ static final long serialVersionUID = -7034868990745766939L; private double balance; public MyException() { } public MyException(String message, double balance) { super(message); this.balance = balance; } public double getBalance() { return balance; } }
像這樣可以實現一些自定義的邏輯,丟擲異常時也可以攜帶自定義的資訊:throw new MyException("餘額不足",-1);再通過e.getBalance()取得自定義的資訊。