異常就是程序運行過程中阻止當前方法或作用域繼續執行的問題;
任何程序都不能保證完全正常運行,當發生異常時,需要我們去處理異常,特別是一些比較重要的場景,異常處理的邏輯也會比較復雜,比如:給用戶提示、保存當前用戶操作或改動、未完成的業務回滾、釋放程序占用的資源等。
在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語句塊,釋放系統資源的占用,比如網絡連接、數據庫連接、文件關閉等;
什么時候怎么使用異常,還需要自己以后在開發中慢慢的熟悉