1. 程式人生 > >java專案中異常處理情況

java專案中異常處理情況

一,基本概念

  異常是程式在執行時出現的不正常情況。是Java按照面向物件的思想將問題進行物件封裝。這樣就方便於操作問題以及處理問題。
  異常處理的目的是提高程式的健壯性。你可以在catch和finally程式碼塊中給程式一個修正機會,使得程式不因不可控制的異常而影響程式的流程。同時,通過獲取Java異常資訊,也為程式的開發維護提供了方便。
Java異常類層次結構圖


Java中的異常用物件來處理,並定義java.lang.Throwable作為所有異常的超類。Throwable分成了兩個不同的分支,Exception(異常)和 Error(錯誤);

其中異常類Exception又分為執行時異常(RuntimeException)和非執行時異常。或不受檢查異常(Unchecked Exception)和檢查異常(Checked Exception);

異常是針對 方法 來說的,丟擲、宣告丟擲、捕獲和處理異常都是在方法中進行的;

Java異常處理通過5個關鍵字try、catch、throw、throws、finally進行管理;

Error(錯誤):災難性的致命的錯誤,是程式無法控制和處理的。

  Error類物件由 Java 虛擬機器生成並丟擲,大多數錯誤與程式碼編寫者所執行的操作無關。例如,Java虛擬機器執行錯誤、記憶體溢位。這些異常發生時,Java虛擬機器(JVM)一般會選擇執行緒終止;還有發生在虛擬機器試圖執行應用時,如類定義錯誤、連結錯誤。這些錯誤是不可查的,因為它們在應用程式的控制和處理能力之外,而且絕大多數是程式執行時不允許出現的狀況。對於設計合理的應用程式來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。
Exception(異常):通常情況下是可以被程式處理的,並且在程式中應該儘可能的去處理這些異常。。

執行時異常和受檢查異常

執行時異常 (unChecked異常):

  RuntimeException類及其子類都被稱為執行時異常。這些異常一般是由程式邏輯錯誤引起的,屬於應該解決的Bug,程式應該從邏輯角度避免這類異常的發生,不推薦try-catch來捕獲處理,但是有時候為了增強使用者體驗,保證Crash次數降到最低,會人為捕捉一些執行時異常。這種異常的特點是Java編譯器不去檢查它,也就是說,當程式中可能出現這類異常時,即使沒有用try-catch語句捕獲它,也沒有用throws字句宣告丟擲它,還是會編譯通過。但在執行時會被系統自動丟擲。
非執行時異常 (checked異常):

  除了RuntimeException類及其子類外,其他的Exception類及其子類都屬於非執行時異常,從程式語法角度講是必須進行處理的異常,如果不處理程式就不能編譯通過。
異常轉型和異常鏈:

  我們做的JEE專案時候,一般會有三層的結構:持久層、邏輯層、展現層。異常也是如此的,當我們各個層之間傳遞異常,我們就需要先封裝,然後傳遞。
異常鏈示例

catch (SQLException e)
{
throw new JdbcException(e);
}


三、異常處理機制

  在 Java 應用程式中,異常處理機制為:丟擲異常,捕捉異常。
丟擲異常
  當一個方法出現錯誤引發異常時,方法建立異常物件並交付執行時系統,異常物件中包含了異常型別和異常出現時的程式狀態等異常資訊。執行時系統負責尋找處置異常的程式碼並執行。
  該方法的呼叫者必須處理或者重新丟擲該異常。當方法的呼叫者無力處理該異常的時候,應該繼續丟擲,所經方法都層層上拋獲取的異常,若最終都沒有被處理,將交由虛擬機器處理。處理也很簡單,就是列印異常訊息和堆疊資訊,記錄日誌。
捕捉異常
  在方法丟擲異常之後,執行時系統將轉為尋找合適的異常處理器(exception handler)。潛在的異常處理器是異常發生時依次存留在呼叫棧中的方法的集合。當異常處理器所能處理的異常型別與方法丟擲的異常型別相符時,即為合適的異常處理器。
  執行時系統從發生異常的方法開始,依次回查呼叫棧中的方法,直至找到含有合適異常處理器的方法並執行。當執行時系統遍歷呼叫棧而未找到合適的異常處理器,如果出現異常的執行緒為主執行緒,則整個程式執行終止;如果非主執行緒,則終止該執行緒,其他執行緒繼續執行。
  在方法中用try-catch語句捕獲並處理異常,catach語句可以有多個,用來匹配處理異常。並且儘量將捕獲底層異常類的catch子句放在前面。
  異常總是先被丟擲,後被捕捉的。
Java規定
  對於可查異常必須捕捉、或者宣告丟擲。允許忽略不可查的RuntimeException和Error。
  RuntimeException由業務邏輯保證。
3.1、丟擲異常例項(throws 和 throw)

public class Throws
 {
 public static void main(String[] args) throws Exception
 {//丟擲異常類
 System.out.println(10 / 0);
 throw new Exception("丟擲異常物件");
 //System.out.println("throw後面的程式碼不再執行");
 }
}


3.2、捕獲異常例項(try-catch 和 finally)

import java.util.InputMismatchException;
import java.util.Scanner;

public class TryCatch {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

try { //可能會發生異常的程式程式碼
System.out.print("請輸入一個數字:");
int a = input.nextInt();
} catch (InputMismatchException e) {// 捕捉異常
System.err.println("資料型別不符!");
e.printStackTrace();
System.err.println(e.getMessage());
// return; 若捕捉到異常會先執行finally, 再return。
} catch (Exception e) {// catch 先寫子類,再寫父類
System.out.println("再捕捉一次!");
// System.exit(0);
} finally {// 除非執行System.exit(0),否則都會執行
System.out.println("finally 被執行!");
// 應用舉例:確保關閉資料庫,關閉流
}

System.out.println("我還是被執行了!");
// 如果提前return,則不執行了
}
}

throw 和throws關鍵字的區別
throw用於丟擲異常物件,後面跟的是異常物件;throw用在方法內。
throws用於丟擲異常類,後面跟的異常類名,可以跟多個,用逗號隔開。throws用在方法方法簽名上。
通常情況:方法內容如果有throw,丟擲異常物件,並沒有進行處理,那麼方法上一定要宣告,否則編譯失敗。
四、Java常見異常

4.1、Error

LinkageError:連結錯誤;
ThreadDeath:執行緒死鎖;
OutOfMemoryError:記憶體溢位;
StackOverflowError :堆疊溢位;
NoClassDefFoundError:類定義錯誤;
Virtual MachineError:虛擬機器執行錯誤。
4.2、執行時異常(unChecked異常)

SecurityException:安全性異常;
NullPointerException:空指標異常;
ClassCastException:型別強制轉換異常;
ClassNotFoundException:找不到類異常;
IllegalArgumentException:非法引數異常;
NegativeArraySizeException:陣列長度為負異常;
ArithmeticException:算術條件異常。如:整數除零;
ArrayIndexOutOfBoundsException:陣列下標越界異常;
ArrayStoreException:陣列中包含不相容的值丟擲的異常;
StringIndexOutOfBoundsException:字串下標越界異常;
ArrayStoreException:向陣列中存放與宣告型別不相容物件異常;
4.3、非執行時異常(checked異常)

IOException:輸入輸出流異常;
SQLException:資料庫操作異常;
EOFException:檔案已結束異常;
TimeoutException:執行超時異常;
DataFormatException:資料格式化異常;
NoSuchFieldException:沒有匹配的屬性異常;
ClassNotFoundException:沒有匹配的類異常;
FileNotFoundException:沒有匹配的檔案異常;
NoSuchMethodException:沒有匹配的方法異常;
4.4、Throwable類的主要方法

public String getMessage():返回關於發生的異常的詳細資訊。這個訊息在Throwable 類的建構函式中初始化了。
public Throwable getCause():返回一個Throwable 物件代表異常原因。
public String toString():使用getMessage()的結果返回類的串級名字。
public void printStackTrace():列印toString()結果和棧層次到System.err,即錯誤輸出流。
public StackTraceElement [] getStackTrace():返回一個包含堆疊層次的陣列。下標為0的元素代表棧頂,最後一個元素代表方法呼叫堆疊的棧底。
public Throwable fillInStackTrace():用當前的呼叫棧層次填充Throwable 物件棧層次,新增到棧層次任何先前資訊中。
五、自定義異常例項

例項一:

class UserException extends Exception { // 繼承父類
public UserException() {
super();
}
public UserException(String message) {
super(message);
}
}

public void activatioin(String code) throws UserException {
try {
User user = userDao.findByCode(code);
if(user == null) throw new UserException("無效的啟用碼!");
if(user.isStatus()) throw new UserException("您已經啟用過了");
userDao.updateStatus(user.getUid(), true); // 修改狀態
} catch(SQLException e) {
throw new RuntimeException(e);
}
}

例項二:

package Test; 
import java.lang.Exception; 
public class TestException { 
static int quotient(int x, int y) throws MyException { // 定義方法丟擲異常 
if (y < 0) { // 判斷引數是否小於0 
throw new MyException("除數不能是負數"); // 異常資訊 
} 
return x/y; // 返回值 
} 
public static void main(String args[]) { // 主方法 
int a =3; 
int b =0; 
try { // try語句包含可能發生異常的語句 
int result = quotient(a, b); // 呼叫方法quotient() 
} catch (MyException e) { // 處理自定義異常 
System.out.println(e.getMessage()); // 輸出異常資訊 
} catch (ArithmeticException e) { // 處理ArithmeticException異常 
System.out.println("除數不能為0"); // 輸出提示資訊 
} catch (Exception e) { // 處理其他異常 
System.out.println("程式發生了其他的異常"); // 輸出提示資訊 
} 
}

} 
class MyException extends Exception { // 建立自定義異常類 
String message; // 定義String型別變數 
public MyException(String ErrorMessagr) { // 父類方法 
message = ErrorMessagr; 
}

public String getMessage() { // 覆蓋getMessage()方法 
return message; 
} 
} 


六、Java異常處理的原則和技巧

不要把自己能處理的異常拋給別人;
catch塊儘量保持一個塊捕獲一類異常;
細化異常的型別,不要不管什麼型別的異常都寫成Excetpion;
避免過大的try塊,不要把不會出現異常的程式碼放到try塊裡面;

如果把父類的異常放到前面,後面的catch語句塊將得不到執行的機會;

儘量將異常統一拋給上層呼叫者,由上層呼叫者統一決定如何進行處理。
不要用try-catch參與控制程式流程,異常控制的根本目的是處理程式的非正常情況;
只要不是retry或者queue的情況,基本上所有的異常都是需要繼續向上拋的,最終交給頂層異常處理機制(應用或者容器)。

Java異常處理三原則

具體明確

提早丟擲

通過提早丟擲異常(又稱"迅速失敗"),異常得以清晰又準確。堆疊資訊立即反映出什麼出了錯(提供了非法引數值),為什麼出錯(檔名不能為空值),以及哪裡出的錯(readPreferences()的前部分)。
延遲捕獲

異常發生時,不應立即捕獲,而是應該考慮當前作用域是否有有能力處理這一異常的能力,如果沒有,則應將該異常繼續向上丟擲,交由更上層的作用域來處理。
如何記錄異常(寫入日誌)

在異常最開始發生的地方進行日誌資訊記錄;
如果捕獲到一個異常,但是這個異常是可以處理的。則無須記錄異常;
捕獲到一個未記錄過的異常或外部系統異常時,應該記錄異常的詳細資訊。
記錄checked異常還是unChecked異常

如果一個異常是可以恢復的,可以被呼叫者正確處理的,使用checked異常;
如果一個異常是致命的,不可恢復的。或者呼叫者去捕獲它沒有任何益處,使用unChecked異常;
在使用unChecked異常時,必須在在方法宣告中詳細的說明該方法可能會丟擲的unChekced異常。由呼叫者自己去決定是否捕獲unChecked異常;
受檢異常儘可能轉化為非受檢異常。


在類繼承的時候,方法覆蓋時如何進行異常處理

如果父類的方法宣告一個異常,則子類在重時宣告的異常範圍應該不小於 父類;
如果父類或者介面中的方法沒有丟擲過異常,那麼子類是不可以丟擲異常的,如果子類的覆蓋的方法中出現了異常,只能try不能throws;
如果這個異常子類無法處理,已經影響了子類方法的具體運算,這時可以在子類方法中,通過throw丟擲RuntimeException異常或者其子類,這樣,子類的方法上是不需要throws宣告的。