異常就是程式運作過程中阻止目前方法或作用域繼續執行的問題;
任何程式都不能保證完全正常運作,當發生異常時,需要我們去處理異常,特别是一些比較重要的場景,異常處理的邏輯也會比較複雜,比如:給使用者提示、儲存目前使用者操作或改動、未完成的業務復原、釋放程式占用的資源等。
在Java中,Throwable異常類是所有異常類的祖先,任何異常類都繼承于Throwable類;
Throwable類主要有兩個子類:Error類、Exception類
Error異常類是系統異常,比如:虛拟機錯誤(VirtualMachineError)、線程死鎖(ThreadDeath)等,Error類異常一旦發生,程式将會崩潰
Exception是開發中我們最常見的一般異常,這種異常原因可能是程式代碼編寫錯誤,環境問題,使用者輸入錯誤等異常
Exception異常一般分為:運作時異常(RuntimeException)也稱為非檢查異常、檢查異常;
非檢查異常常見的有:輸出空指針時的異常,數組下标越界異常,類型轉換異常,算術異常(比如0作為分母)等,運作時異常會由Java虛拟機自動捕獲,自動抛出,一般是我們寫的代碼本身有問題,需要改進我們的代碼來解決
檢查異常的原因有可能是:檔案異常(不存在或者權限)、資料庫連接配接異常、網絡連接配接異常等,這種異常系統不會自動捕獲,需要我們手動添加捕獲處理的語句
我們通常使用try-catch以及try-catch-finally代碼塊來處理異常
try代碼塊中是可能發生異常的語句,當程式确實發生異常了,try塊中程式會中止執行,并且抛出異常給catch塊進行處理,catch根據需要去處理異常、記錄錯誤日志等,看一個簡單示例:
1 import java.util.Scanner;
2
3 public class Ceshi {
4 public static void main(String[] args){
5 try{
6 System.out.println("請輸入一個整數:");
7 Scanner input = new Scanner(System.in);
8 int a = input.nextInt();
9 System.out.println("您輸入的是:" + a);
10 }catch(Exception e){
11 System.out.println("輸入異常");
12 e.printStackTrace(); //列印異常資訊
13 }
14 System.out.println("程式執行結束");
15 }
16 }
這是一個最簡單的異常處理,通過Scanner擷取使用者輸入,當使用者正确輸入時程式正常執行,當然catch塊不會被執行,但是使用者如果輸入的不是整數,那麼就會抛出異常給catch塊,可以利用printStackTrace()方法列印具體的異常,注意無論程式是否異常try-catch外的語句都會被正常執行,錯誤結果如下:

根據結果可以看到我們輸入字元串"3s"之後,抛出了異常并且提示輸入異常,最後但是try-catch後面的語句正常執行,抛出的e.printStackTrace()會在最後被列印出來,可以看出來是Ceshi.java第就行發生了異常産生了終止,那麼就是在a接收輸入這一行語句中發生的異常,那麼在這一行之後的所有try塊中的語句便終止執行
另外如果try中的代碼會抛出好幾個類型的異常,那麼我們需要多個catch塊來處理,并且加上finally進行善後處理工作
1 import java.util.Scanner;
2 import java.util.InputMismatchException;
3 import java.lang.ArithmeticException;
4 public class Ceshi {
5 public static void main(String[] args){
6 Scanner input = new Scanner(System.in);
7 try{
8 System.out.println("請輸入分子:");
9 int a = input.nextInt();
10 System.out.println("您輸入分母:");
11 int b = input.nextInt();
12 System.out.println("計算結果是:" + a*1.0/b);
13 }catch(InputMismatchException e){
14 System.out.println("請輸入整數");
15 e.printStackTrace(); //列印異常資訊
16 }catch(ArithmeticException e){
17 System.out.println("分母不能為0");
18 e.printStackTrace(); //列印異常資訊
19 }catch(Exception e){
20 System.out.println("其他未知異常");
21 e.printStackTrace();
22 }finally{
23 input.close();
24 }
25 System.out.println("程式執行結束");
26 }
27 }
以上的處理就比較合理了,首先保證輸入是整數,如果都是整數那麼分母為0也會抛出異常,最後如果還有我們考慮不到的異常,那麼就通過Exception父類抛出異常,catch異常塊從上到下一般是是由小到大或者由子類到父類的異常類抛出,就是從作用範圍來說從細節到整體,Exception異常類抛出必須放在最後面,這樣能抛出我們開發中遇到的所有異常,另外finally塊建議帶上,當遇到異常時,他可以釋放前面還未操作的系統資源,比如例子中的關閉輸入,這樣能提高程式的健壯性,如果try和catch中有傳回值,那麼finally中的語句會在try和catch語句塊中的return傳回值傳回到調用者之前,獲得該傳回值,我們可以在程式中輸出他們,但是放在try-catch-finally外傳回值時在finally是無法擷取到的,隻能擷取前面的變量值
Java中方法異常抛出,因為很多代碼我們會寫到方法中,為了便于管理,我們可以在專門的方法中處理異常,是以我們可以将方法中的異常向上抛出,可以寫一個方法來簡單抛出異常,代碼如下:
1 public void divide(int a,int b) throws Exception {
2 if(b == 0){
3 throw new Exception("除數不能為零!");
4 }else{
5 System.out.println("結果為:" + a*1.0/b);
6 }
7 }
當該方法被調用時,那麼如果發生異常,異常将抛出到調用的語句塊中,我們可以在調用的時候進行處理,比如:
1 public void complte() {
2 try{
3 divide(5,0); //此時發生異常,調用方法将異常抛出到這裡
4 }catch(Exception e){
5 System.out.println(e.getMessage()); //此處捕獲異常,将方法中定義的異常資訊抛出
6 }
7 }
這樣就把方法中的異常抛出并進行了處理,另外我們還可以不在complte方法中抛出,還可以向上抛出,由上面調用該方法時抛出異常,代碼如下:
public void complte() throws Exception {
/**
* 省略方法中的代碼
*/
divide(5,0); //将裡面的異常抛出到調用complte方法的位置
}
這樣的話異常繼續向上抛出,最終還是按照第二段代碼的方式來處理異常,是以用throws關鍵字聲明此方法向上抛出異常,用throw關鍵字來抛出異常
自定義異常
除了利用系統的異常我們還可以自定義異常,以便适應我們情景的需要,簡單定義個異常類:
1 public class CeshiException extends Exception {
2
3 public CeshiException(){
4
5 }
6
7 public CeshiException(String message){
8 super(message);
9 }
10 }
注意,自定義異常類必須繼承于Exception異常類,裡面定義了一個有參數的構造方法來自定義異常資訊,無參的構造方法是為了執行個體化類時,預設不會發生錯誤,那麼我們可以在方法中具體來使用這個自定義異常類了:
1 public class ChainTest {
2
3 /**
4 * test1():抛出自定義異常
5 * test2():調用test1(),捕獲自定義異常,并且包裝成運作時異常,抛出新異常
6 * main方法中,調用test2(),嘗試捕獲test2()方法抛出的異常
7 */
8 public static void main(String[] args) {
9 ChainTest ct = new ChainTest();
10 try{
11 ct.test2();
12 }catch(Exception e){
13 e.printStackTrace();
14 }
15 }
16
17 public void test1() throws CeshiException{
18 throw new CeshiException("原始自定義異常抛出");
19 }
20
21 public void test2(){
22 try {
23 test1();
24 } catch (CeshiException e) {
25 // TODO Auto-generated catch block
26 RuntimeException newExc =
27 new RuntimeException("抛出新運作時異常");
28 newExc.initCause(e); //引用原始異常方法,異常鍊
29 throw newExc;
30 }
31 }
32 }
根據代碼可以看出,main方法調用test2方法并捕獲test2方法抛出的異常,而test2方法中運作test1并捕獲test1方法中抛出的自定義異常,并且自己也抛出一個新的運作時異常抛出到main方法中,而test1方法通過聲明自定義異常類實作了抛出自定義異常類中的異常方法,将異常抛出到test2中,這樣就好比一連串的異常抛出和異常處理,同時結合了自定義異常,這樣就形成了一個小型的異常鍊,就好像鍊式反應一樣去抛出異常
最後,總結一下,通過try-catch來處理異常,并不能避免錯誤的存在性,而是盡量提高程式的健壯性,減小程式錯誤而帶來的安全風險和損失,我們不能一味的用try-catch來屏蔽錯誤,我們應該采用合理的邏輯算法來解決程式設計的不足,try-catch隻是一個作為輔助使用,不可以過分依賴;
在多重catch塊之後,最好加個catch(Exception e){}來處理其他可能會被遺漏的未知的異常,對于不太确定的異常,可以加上try-catch來處理潛在的風險;
對于異常一定要盡量去處理,千萬不要隻是簡單地使用e.printStackTrace();來列印錯誤資訊,這樣就失去了異常處理的意義;
具體如何處理異常,應該根據不同的業務需求和異常類型來确定;
最後要善于在try-catch塊後面添加finally語句塊,釋放系統資源的占用,比如網絡連接配接、資料庫連接配接、檔案關閉等;
什麼時候怎麼使用異常,還需要自己以後在開發中慢慢的熟悉