我們們一起聊聊Java異常

AskHarries發表於2018-07-06

什麼是異常

程式執行時,發生的不被期望的事件,它阻止了程式按照程式設計師的預期正常執行,這就是異常。異常發生時,是任程式自生自滅,立刻退出終止,還是輸出錯誤給使用者?或者用C語言風格:用函式返回值作為執行狀態?。

Java提供了更加優秀的解決辦法:異常處理機制。

異常處理機制能讓程式在異常發生時,按照程式碼的預先設定的異常處理邏輯,針對性地處理異常,讓程式盡最大可能恢復正常並繼續執行,且保持程式碼的清晰。
Java中的異常可以是函式中的語句執行時引發的,也可以是程式設計師通過throw 語句手動丟擲的,只要在Java程式中產生了異常,就會用一個對應型別的異常物件來封裝異常,JRE就會試圖尋找異常處理程式來處理異常。

Throwable類是Java異常型別的頂層父類,一個物件只有是 Throwable 類的(直接或者間接)例項,他才是一個異常物件,才能被異常處理機制識別。JDK中內建了一些常用的異常類,我們也可以自定義異常。

Java異常的分類和類結構圖

Java標準庫內建了一些通用的異常,這些類以Throwable為頂層父類。

Throwable又派生出Error類和Exception類。

錯誤:Error類以及他的子類的例項,代表了JVM本身的錯誤。錯誤不能被程式設計師通過程式碼處理,Error很少出現。因此,程式設計師應該關注Exception為父類的分支下的各種異常類。

異常:Exception以及他的子類,代表程式執行時傳送的各種不期望發生的事件。可以被Java異常處理機制使用,是異常處理的核心。

總體上我們根據Javac對異常的處理要求,將異常類分為2類。

非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求在程式處理這些異常。所以如果願意,我們可以編寫程式碼處理(使用try…catch…finally)這樣的異常,也可以不處理。對於這些異常,我們應該修正程式碼,而不是去通過異常處理器處理 。這樣的異常發生的原因多半是程式碼寫的有問題。如除0錯誤ArithmeticException,錯誤的強制型別轉換錯誤ClassCastException,陣列索引越界ArrayIndexOutOfBoundsException,使用了空物件NullPointerException等等。

檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強制要求程式設計師為這樣的異常做預備處理工作(使用try…catch…finally或者throws)。在方法中要麼用try-catch語句捕獲它並處理,要麼用throws子句宣告丟擲它,否則編譯不會通過。這樣的異常一般是由程式的執行環境導致的。因為程式可能被執行在各種未知的環境下,而程式設計師無法干預使用者如何使用他編寫的程式,於是程式設計師就應該為這樣的異常時刻準備著。如SQLException , IOException,ClassNotFoundException 等。

需要明確的是:檢查和非檢查是對於javac來說的,這樣就很好理解和區分了。

為什麼要自定義異常

1.我們在工作的時候,專案是分模組或者分功能開發的 ,基本不會你一個人開發一整個專案,使用自定義異常類就統一了對外異常展示的方式。

2.有時候我們遇到某些校驗或者問題時,需要直接結束掉當前的請求,這時便可以通過丟擲自定義異常來結束,如果你專案中使用了SpringMVC比較新的版本的話有控制器增強,可以通過@ControllerAdvice註解寫一個控制器增強類來攔截自定義的異常並響應給前端相應的資訊(關於springMVC控制器增強的知識有空再和大家分享)。

3.自定義異常可以在我們專案中某些特殊的業務邏輯時丟擲異常,比如”中性”.equals(sex),性別等於中性時我們要丟擲異常,而Java是不會有這種異常的。系統中有些錯誤是符合Java語法的,但不符合我們專案的業務邏輯。

4.使用自定義異常繼承相關的異常來丟擲處理後的異常資訊可以隱藏底層的異常,這樣更安全,異常資訊也更加的直觀。自定義異常可以丟擲我們自己想要丟擲的資訊,可以通過丟擲的資訊區分異常發生的位置,根據異常名我們就可以知道哪裡有異常,根據異常提示資訊進行程式修改。比如空指標異常NullPointException,我們可以丟擲資訊為“xxx為空”定位異常位置,而不用輸出堆疊資訊。

如何自定義異常

如果要自定義異常類,則擴充套件Exception類即可,因此這樣的自定義異常都屬於檢查異常(checked exception)。如果要自定義非檢查異常,則擴充套件自RuntimeException。

按照國際慣例,自定義的異常應該總是包含如下的建構函式:

  • 一個無參建構函式
  • 一個帶有String引數的建構函式,並傳遞給父類的建構函式。
  • 一個帶有String引數和Throwable引數,並都傳遞給父類建構函式
  • 一個帶有Throwable 引數的建構函式,並傳遞給父類的建構函式。

下面是IOException類的完整原始碼,可以借鑑。

/**
* MIT License
* Copyright (c) 2018 haihua.liu
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package cn.liuhaihua.web.exception;

/**
* @ClassName: ServiceException
* @Description: 自定義service異常
* @author Liuhaihua
* @date 2018年7月5日
*
*/
public class ServiceException extends RuntimeException {

private static final long serialVersionUID = 1L;

/**
* 錯誤編碼
*/
private String errorCode;

/**
* 訊息是否為屬性檔案中的Key
*/
private boolean propertiesKey = true;

/**
* 構造一個基本異常.
*
* @param message
* 資訊描述
*/
public ServiceException(String message)
{
super(message);
}

/**
* 構造一個基本異常.
*
* @param errorCode
* 錯誤編碼
* @param message
* 資訊描述
*/
public ServiceException(String errorCode, String message)
{
this(errorCode, message, true);
}

/**
* 構造一個基本異常.
*
* @param errorCode
* 錯誤編碼
* @param message
* 資訊描述
*/
public ServiceException(String errorCode, String message, Throwable cause)
{
this(errorCode, message, cause, true);
}

/**
* 構造一個基本異常.
*
* @param errorCode
* 錯誤編碼
* @param message
* 資訊描述
* @param propertiesKey
* 訊息是否為屬性檔案中的Key
*/
public ServiceException(String errorCode, String message, boolean propertiesKey)
{
super(message);
this.setErrorCode(errorCode);
this.setPropertiesKey(propertiesKey);
}

/**
* 構造一個基本異常.
*
* @param errorCode
* 錯誤編碼
* @param message
* 資訊描述
*/
public ServiceException(String errorCode, String message, Throwable cause, boolean propertiesKey)
{
super(message, cause);
this.setErrorCode(errorCode);
this.setPropertiesKey(propertiesKey);
}

/**
* 構造一個基本異常.
*
* @param message
* 資訊描述
* @param cause
* 根異常類(可以存入任何異常)
*/
public ServiceException(String message, Throwable cause)
{
super(message, cause);
}

public String getErrorCode()
{
return errorCode;
}

public void setErrorCode(String errorCode)
{
this.errorCode = errorCode;
}

public boolean isPropertiesKey()
{
return propertiesKey;
}

public void setPropertiesKey(boolean propertiesKey)
{
this.propertiesKey = propertiesKey;
}

}

異常使用注意事項

當使用多個catch語句塊來捕獲異常時,需要將父類的catch語句塊放到子型別的catch塊之後,這樣才能保證後續的catch可能被執行,否則子型別的catch將永遠無法到達,Java編譯器會報編譯錯誤。

如果try語句塊中存在return語句,那麼首先會執行finally語句塊中的程式碼,然後才返回。

如果try語句塊中存在System.exit(0)語句,那麼久不會執行finally語句塊的程式碼了,因為System.exit(0)會終止當前執行的JVM。程式在JVM終止前結束執行。

我們們一起聊聊Java異常



相關文章