溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中與那些異常處理方式

發(fā)布時(shí)間:2021-06-18 15:39:08 來(lái)源:億速云 閱讀:150 作者:Leah 欄目:大數(shù)據(jù)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)Java中與那些異常處理方式,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

什么是異常?

異常的英文單詞是 exception,異常本質(zhì)上是程序上的錯(cuò)誤,包括程序邏輯錯(cuò)誤和系統(tǒng)錯(cuò)誤。比如使用空的引用、數(shù)組下標(biāo)越界、內(nèi)存溢出錯(cuò)誤等,這些都是意外的情況,背離我們程序本身的意圖。錯(cuò)誤在我們編寫程序的過(guò)程中會(huì)經(jīng)常發(fā)生,包括編譯期間和運(yùn)行期間的錯(cuò)誤,在編譯期間出現(xiàn)的錯(cuò)誤有編譯器幫助我們一起修正,然而運(yùn)行期間的錯(cuò)誤便不是編譯器力所能及了,并且運(yùn)行期間的錯(cuò)誤往往是難以預(yù)料的。假若程序在運(yùn)行期間出現(xiàn)了錯(cuò)誤,如果置之不理,程序便會(huì)終止或直接導(dǎo)致系統(tǒng)崩潰,顯然這不是我們希望看到的結(jié)果。

如何對(duì)運(yùn)行期間出現(xiàn)的錯(cuò)誤進(jìn)行處理和補(bǔ)救呢?Java 提供了異常機(jī)制來(lái)進(jìn)行處理,通過(guò)異常機(jī)制來(lái)處理程序運(yùn)行期間出現(xiàn)的錯(cuò)誤。通過(guò)異常機(jī)制,我們可以更好地提升程序的健壯性。

異常分類

Java 把異常當(dāng)作對(duì)象來(lái)處理,并定義一個(gè)基類 java.lang.Throwable 作為所有異常的超類。

Java 包括三種類型的異常: 檢查性異常(checked exceptions)、非檢查性異常(unchecked Exceptions) 和錯(cuò)誤(errors)。

  • 檢查性異常(checked exceptions) 是必須在在方法的 throws 子句中聲明的異常。它們擴(kuò)展了異常,旨在成為一種“在你面前”的異常類型。JAVA希望你能夠處理它們,因?yàn)樗鼈円阅撤N方式依賴于程序之外的外部因素。檢查的異常表示在正常系統(tǒng)操作期間可能發(fā)生的預(yù)期問(wèn)題。 當(dāng)你嘗試通過(guò)網(wǎng)絡(luò)或文件系統(tǒng)使用外部系統(tǒng)時(shí),通常會(huì)發(fā)生這些異常。 大多數(shù)情況下,對(duì)檢查性異常的正確響應(yīng)應(yīng)該是稍后重試,或者提示用戶修改其輸入。

  • 非檢查性異常(unchecked Exceptions) 是不需要在throws子句中聲明的異常。 由于程序錯(cuò)誤,JVM并不會(huì)強(qiáng)制你處理它們,因?yàn)樗鼈兇蠖鄶?shù)是在運(yùn)行時(shí)生成的。 它們擴(kuò)展了 RuntimeException。 最常見(jiàn)的例子是 NullPointerException, 未經(jīng)檢查的異??赡懿粦?yīng)該重試,正確的操作通常應(yīng)該是什么都不做,并讓它從你的方法和執(zhí)行堆棧中出來(lái)。

  • 錯(cuò)誤(errors) 是嚴(yán)重的運(yùn)行時(shí)環(huán)境問(wèn)題,肯定無(wú)法恢復(fù)。 例如 OutOfMemoryError,LinkageError 和 StackOverflowError,通常會(huì)讓程序崩潰。

所有不是 Runtime Exception 的異常,統(tǒng)稱為 Checked Exception,又被稱為檢查性異常。這類異常的產(chǎn)生不是程序本身的問(wèn)題,通常由外界因素造成的。為了預(yù)防這些異常產(chǎn)生時(shí),造成程序的中斷或得到不正確的結(jié)果,Java 要求編寫可能產(chǎn)生這類異常的程序代碼時(shí),一定要去做異常的處理。

Java 語(yǔ)言將派生于 RuntimeException 類或 Error 類的所有異常稱為非檢查性異常。

Java 異常層次結(jié)構(gòu)圖如下圖所示:

Java中與那些異常處理方式

在了解了異常的基本概念以及分類后,現(xiàn)在讓我們開(kāi)始探索異常處理的最佳實(shí)踐吧。

異常處理最佳實(shí)踐

不要忽略捕捉的異常

catch (NoSuchMethodException e) {
   return null;
}

雖然捕捉了異常但是卻沒(méi)有做任何處理,除非你確信這個(gè)異??梢院雎?,不然不應(yīng)該這樣做。這樣會(huì)導(dǎo)致外面無(wú)法知曉該方法發(fā)生了錯(cuò)誤,無(wú)法確定定位錯(cuò)誤原因。

在你的方法里拋出定義具體的檢查性異常

public void foo() throws Exception { //錯(cuò)誤方式
}

一定要避免出現(xiàn)上面的代碼示例,它破壞了檢查性異常的目的。 聲明你的方法可能拋出的具體檢查性異常,如果只有太多這樣的檢查性異常,你應(yīng)該把它們包裝在你自己的異常中,并在異常消息中添加信息。 如果可能的話,你也可以考慮代碼重構(gòu)。

public void foo() throws SpecificException1, SpecificException2 { //正確方式
}

捕獲具體的子類而不是捕獲 Exception 類

try {
   someMethod();
} catch (Exception e) { //錯(cuò)誤方式
   LOGGER.error("method has failed", e);
}

捕獲異常的問(wèn)題是,如果稍后調(diào)用的方法為其方法聲明添加了新的檢查性異常,則開(kāi)發(fā)人員的意圖是應(yīng)該處理具體的新異常。如果你的代碼只是捕獲異常(或 Throwable),永遠(yuǎn)不會(huì)知道這個(gè)變化,以及你的代碼現(xiàn)在是錯(cuò)誤的,并且可能會(huì)在運(yùn)行時(shí)的任何時(shí)候中斷。

永遠(yuǎn)不要捕獲 Throwable 類

這是一個(gè)更嚴(yán)重的麻煩,因?yàn)?Java Error 也是 Throwable 的子類,Error 是 JVM 本身無(wú)法處理的不可逆轉(zhuǎn)的條件,對(duì)于某些 JVM 的實(shí)現(xiàn),JVM 可能實(shí)際上甚至不會(huì)在 Error 上調(diào)用 catch 子句。

始終正確包裝自定義異常中的異常,以便堆棧跟蹤不會(huì)丟失

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " + e.getMessage());  //錯(cuò)誤方式
}

這破壞了原始異常的堆棧跟蹤,并且始終是錯(cuò)誤的,正確的做法是:

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " , e);  //正確方式
}

要么記錄異常要么拋出異常,但不要一起執(zhí)行

catch (NoSuchMethodException e) {  
//錯(cuò)誤方式 
   LOGGER.error("Some information", e);
   throw e;
}

正如上面的代碼中,記錄和拋出異常會(huì)在日志文件中產(chǎn)生多條日志消息,代碼中存在單個(gè)問(wèn)題,并且對(duì)嘗試分析日志的同事很不友好。

finally 塊中永遠(yuǎn)不要拋出任何異常

try {
  someMethod();  //Throws exceptionOne
} finally {
  cleanUp();    //如果finally還拋出異常,那么exceptionOne將永遠(yuǎn)丟失
}

只要 cleanUp() 永遠(yuǎn)不會(huì)拋出任何異常,上面的代碼沒(méi)有問(wèn)題,但是如果 someMethod() 拋出一個(gè)異常,并且在 finally 塊中,cleanUp() 也拋出另一個(gè)異常,那么程序只會(huì)把第二個(gè)異常拋出來(lái),原來(lái)的第一個(gè)異常(正確的原因)將永遠(yuǎn)丟失。如果在 finally 塊中調(diào)用的代碼可能會(huì)引發(fā)異常,請(qǐng)確保要么處理它,要么將其記錄下來(lái)。永遠(yuǎn)不要讓它從 finally 塊中拋出來(lái)。

始終只捕獲實(shí)際可處理的異常

catch (NoSuchMethodException e) {
   throw e; //避免這種情況,因?yàn)樗鼪](méi)有任何幫助
}

這是最重要的概念,不要為了捕捉異常而捕捉,只有在想要處理異常時(shí)才捕捉異常,或者希望在該異常中提供其他上下文信息。如果你不能在 catch 塊中處理它,那么最好的建議就是不要只為了重新拋出它而捕獲它。

不要使用 printStackTrace() 語(yǔ)句或類似的方法

完成代碼后,切勿忽略 printStackTrace(),最終別人可能會(huì)得到這些堆棧,并且對(duì)于如何處理它完全沒(méi)有任何方法,因?yàn)樗粫?huì)附加任何上下文信息。

對(duì)于不打算處理的異常,直接使用 finally

try {
  someMethod();  //Method 2
} finally {
  cleanUp();    //do cleanup here
}

這是一個(gè)很好的做法,如果在你的方法中你正在訪問(wèn) Method 2,而 Method 2 拋出一些你不想在 Method 1 中處理的異常,但是仍然希望在發(fā)生異常時(shí)進(jìn)行一些清理,然后在 finally 塊中進(jìn)行清理,不要使用 catch 塊。

記住早 throw 晚 catch 原則

這可能是關(guān)于異常處理最著名的原則,簡(jiǎn)單說(shuō),應(yīng)該盡快拋出(throw)異常,并盡可能晚地捕獲(catch)它。應(yīng)該等到有足夠的信息來(lái)妥善處理它。

這個(gè)原則隱含地說(shuō),你將更有可能把它放在低級(jí)方法中,在那里你將檢查單個(gè)值是否為空或不適合。而且你會(huì)讓異常堆棧跟蹤上升好幾個(gè)級(jí)別,直到達(dá)到足夠的抽象級(jí)別才能處理問(wèn)題。

在異常處理后清理資源

如果你正在使用數(shù)據(jù)庫(kù)連接或網(wǎng)絡(luò)連接等資源,請(qǐng)確保清除它們。如果你正在調(diào)用的 API 僅使用非檢查性異常,則仍應(yīng)使用 try-finally 塊來(lái)清理資源。 在 try 模塊里面訪問(wèn)資源,在 finally 里面最后關(guān)閉資源。即使在訪問(wèn)資源時(shí)發(fā)生任何異常,資源也會(huì)優(yōu)雅地關(guān)閉。

只拋出和方法相關(guān)的異常

相關(guān)性對(duì)于保持應(yīng)用程序清潔非常重要。一種嘗試讀取文件的方法,如果拋出 NullPointerException,那么它不會(huì)給用戶任何相關(guān)的信息。相反,如果這種異常被包裹在自定義異常中,則會(huì)更好。NoSuchFileFoundException 則對(duì)該方法的用戶更有用。

切勿在程序中使用異常來(lái)進(jìn)行流程控制

不要在項(xiàng)目中出現(xiàn)使用異常來(lái)處理應(yīng)用程序邏輯。永遠(yuǎn)不要這樣做,它會(huì)使代碼很難閱讀和理解。

盡早驗(yàn)證用戶輸入以在請(qǐng)求處理的早期捕獲異常

始終要在非常早的階段驗(yàn)證用戶輸入,甚至在達(dá)到 controller 之前,它將幫助你把核心應(yīng)用程序邏輯中的異常處理代碼量降到最低。如果用戶輸入出現(xiàn)錯(cuò)誤,還可以保證與應(yīng)用程序一致。

例如:如果在用戶注冊(cè)應(yīng)用程序中,遵循以下邏輯:

  1. 驗(yàn)證用戶

  2. 插入用戶

  3. 驗(yàn)證地址

  4. 插入地址

  5. 如果出問(wèn)題回滾一切

這是不正確的做法,它會(huì)使數(shù)據(jù)庫(kù)在各種情況下處于不一致的狀態(tài),應(yīng)該首先驗(yàn)證所有內(nèi)容,然后將用戶數(shù)據(jù)置于 dao 層并進(jìn)行數(shù)據(jù)庫(kù)更新。正確的做法是:

  1. 驗(yàn)證用戶

  2. 驗(yàn)證地址

  3. 插入用戶

  4. 插入地址

  5. 如果問(wèn)題回滾一切

一個(gè)異常只能包含在一個(gè)日志中

LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");

不要像上面這樣做,對(duì)多個(gè) LOGGER.debug() 調(diào)用使用多行日志消息可能在你的測(cè)試用例中看起來(lái)不錯(cuò),但是當(dāng)它在具有 100 個(gè)并行運(yùn)行的線程的應(yīng)用程序服務(wù)器的日志文件中顯示時(shí),所有信息都輸出到相同的日志文件,即使它們?cè)趯?shí)際代碼中為前后行,但是在日志文件中這兩個(gè)日志消息可能會(huì)間隔 100 多行。應(yīng)該這樣做:

LOGGER.debug("Using cache sector A, using retry sector B");

將所有相關(guān)信息盡可能地傳遞給異常

有用的異常消息和堆棧跟蹤非常重要,如果你的日志不能定位異常位置,那要日志有什么用呢?

終止掉被中斷線程

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {} //別這樣做
  doSomethingCool();
}

InterruptedException 異常提示應(yīng)該停止程序正在做的事情,比如事務(wù)超時(shí)或線程池被關(guān)閉等。

應(yīng)該盡最大努力完成正在做的事情,并完成當(dāng)前執(zhí)行的線程,而不是忽略 InterruptedException。修改后的程序如下:

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {
    break;
  }
}
doSomethingCool();

對(duì)于重復(fù)的 try-catch,使用模板方法

在代碼中有許多類似的 catch 塊是無(wú)用的,只會(huì)增加代碼的重復(fù)性,針對(duì)這樣的問(wèn)題可以使用模板方法。

例如,在嘗試關(guān)閉數(shù)據(jù)庫(kù)連接時(shí)的異常處理。

class DBUtil{
    public static void closeConnection(Connection conn){
        try{
            conn.close();
        } catch(Exception ex){
            //Log Exception - Cannot close connection
        }
    }
}

這類的方法將在應(yīng)用程序很多地方使用。不要把這塊代碼放的到處都是,而是定義上面的方法,然后像下面這樣使用它:

public void dataAccessCode() {
    Connection conn = null;
    try{
        conn = getConnection();
        ....
    } finally{
        DBUtil.closeConnection(conn);
    }
}

使用 JavaDoc 中記錄應(yīng)用程序中的所有異常

把用 JavaDoc 記錄運(yùn)行時(shí)可能拋出的所有異常作為一種習(xí)慣,其中也盡量包括用戶應(yīng)該遵循的操作,以防這些異常發(fā)生。

上述就是小編為大家分享的Java中與那些異常處理方式了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI