Java 中的異常處理機制

来源:https://www.cnblogs.com/feiyu2/archive/2022/07/18/exception.html
-Advertisement-
Play Games

本篇文章主要介紹了1、Java 中的異常2、如何處理函數拋出的異常3、處理異常的原則4、異常處理時,性能開銷大的地方 ...


本篇文章主要介紹了

  • Java 中的異常
  • 如何處理函數拋出的異常
  • 處理異常的原則
  • 異常處理時,性能開銷大的地方

Java 語言在設計之初就提供了相對完善的異常處理機制。
我們首先介紹一下 Java 中的異常。

介紹 Java 中的異常

異常是程式在運行過程中出現的程式異常事件,異常會中斷正在執行的正常指令流 。
Java 中的異常分為兩大類:Exception 和 Error。


下麵是 Exception 和 Error 的類定義

public class Exception extends Throwable {}
public class Error extends Throwable {}

Exception 和 Error 都繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實例才可以被拋出(throw)或者被捕獲(catch)。
Exception 和 Error 體現了 Java 平臺設計者對不同異常情況的分類。
下麵我們逐一介紹 Error 和 Exception。

介紹 Error

Error 類對象一般是由虛擬機生成並拋出,絕大部分的 Error 都會導致虛擬機自身處於不可恢復的狀態,是程式無法控制和處理的。當出現 Error 時,一般會選擇終止線程。
Error 中最常見的是虛擬機運行錯誤(VirtualMachineError 抽象類)。
虛擬機運行錯誤中最常見的有:

  • 記憶體溢出(OutOfMemoryError):由於記憶體不足,虛擬機沒有可分配的記憶體了,垃圾回收器也不能釋放更多的記憶體,那麼虛擬機拋出 OutOfMemoryError
  • 棧溢出(StackOverflowError):如果一個線程已用的棧大小 超過 配置的允許最大的棧大小,那麼虛擬機拋出 StackOverflowError

介紹 Exception

Exception 有兩種類型「編譯時異常」和「運行時異常」

  • 「編譯時異常」對應 Java 的 Exception 類
  • 「運行時異常」對應 Java 的 RuntimeException 類(RuntimeException 類繼承 Exception 類 )

下麵是 Exception、RuntimeException 類的定義

public class Exception extends Throwable {}
public class RuntimeException extends Exception {}

對於「運行時異常」,我們在編寫代碼的時候,可以不用主動去 try-catch 捕獲(不強制要求),編譯器在編譯代碼的時候,並不會檢查代碼是否有對運行時異常做了處理。
相反,對於「編譯時異常」,我們在編寫代碼的時候,必須主動去 try-catch 獲取 或者 在函數定義中聲明向上拋出異常(throws),否則編譯就會報錯。
所以:

  • 「運行時異常」也叫作非受檢異常(Unchecked Exception)
  • 「編譯時異常」也叫作受檢異常(Checked Exception)

在函數拋出異常的時候,,我們該怎麼處理呢?是吞掉還是向上拋出?
如果選擇向上拋出,我們應該選擇拋出哪種類型的異常呢?是受檢異常還是非受檢異常?
我們下文會對此介紹。


常見的編譯時異常有:

  • FileNotFoundException:當嘗試打開由指定路徑表示的文件失敗時拋出
  • ClassNotFoundException:當應用程式嘗試通過其字元串名稱載入類時拋出,以下三種方法載入
    • Class.forName(java.lang.String)
    • ClassLoader.findSystemClass(java.lang.String)
    • ClassLoader.loadClass(java.lang.String, boolean)

常見的運行時異常有:

  • 非法參數異常(IllegalArgumentException):當傳入了非法或不正確的參數時拋出
  • 空指針異常(NullPointerException):當在需要對象的情況下使用了 null 時拋出。
  • 下標訪問越界異常(IndexOutOfBoundsException):當某種索引(例如數組,字元串或向量)的索引超出範圍時拋出。
  • 類型轉換異常(ClassCastException):當嘗試將對象轉換為不是實例的子類時拋出。
  • 運算異常(ArithmeticException):運算條件出現異常時拋出。例如,“除以零”的整數。

Java 異常類的結構

image.png

如何處理函數拋出的異常

在函數拋出異常的時候,我們該怎麼處理呢?是吞掉還是向上拋出?
如果選擇向上拋出,我們應該選擇拋出哪種類型的異常呢?是受檢異常還是非受檢異常?
下麵我們就對此介紹。

吞掉 or 拋出

在函數拋出異常的時候,我們該怎麼處理?是吞掉還是向上拋出?

總結一下,在函數拋出異常的時候,一般有下麵三種處理方法。

  • 直接吞掉
  • 原封不動地 re-throw
  • 包裝成新的異常 re-throw

直接吞掉。具體的代碼示例如下所示:

public void func1() throws Exception1 {
    // ...
}

public void func2() {
    //...
    try {
        func1();
    } catch (Exception1 e) {
        //吐掉:try-catch列印日誌
        log.warn("...", e);
    }
    //...
}

原封不動地 re-throw。具體的代碼示例如下所示:

public void func1() throws Exception1 {
    // ...
}

//原封不動的re-throw Exception1
public void func2() throws Exception1 {
    //...
    func1();
    //...
}

包裝成新的異常 re-throw。具體的代碼示例如下所示:

public void func1() throws Exception1 {
    // ...
}

public void func2() throws Exception2 {
    //...
    try {
        func1();
    } catch (Exception1 e) {
        // wrap成新的Exception2然後re-throw
        throw new Exception2("...", e);
    }
    //...
}

當我們面對函數拋出異常的時候,應該選擇上面的哪種處理方式呢?我總結了下麵三個參考原則:

  • 如果 func1() 拋出的異常是可以恢復,且 func2() 的調用方並不關心此異常,我們完全可以在 func2() 內將 func1() 拋出的異常吞掉;
  • 如果 func1() 拋出的異常對 func2() 的調用方來說,也是可以理解的、關心的 ,並且在業務概念上有一定的相關性,我們可以選擇直接將 func1 拋出的異常 re-throw;
  • 如果 func1() 拋出的異常太底層,對 func2() 的調用方來說,缺乏背景去理解、且業務概念上無關,我們可以將它重新包裝成調用方可以理解的新異常,然後 re-throw。

應該選擇上面的哪種處理方式,總結來說就是從以下兩個方面進行判斷:

  1. 函數1 拋出的異常是否可以恢復
  2. 函數1 拋出的異常對於 函數2 的調用方來說是否可以理解、關心、業務概念相關

總之,是否往上繼續拋出,要看上層代碼是否關心這個異常。關心就將它拋出,否則就直接吞掉。
是否需要包裝成新的異常拋出,看上層代碼是否能理解這個異常、是否業務相關。如果能理解、業務相關就可以直接拋出,否則就封裝成新的異常拋出。


對於處理函數拋出的異常,我們需要註意:

  • 如果選擇吞掉函數拋出的異常的話,我們必須把異常輸出到日誌系統,方便後續診斷。
  • 如果把異常輸出到日誌系統時,我們在保證診斷信息足夠的同時,也要考慮避免包含敏感信息,因為那樣可能導致潛在的安全問題。

如果我們看 Java 的標準類庫,你可能註意到類似 java.net.ConnectException,出錯信息是類似“ Connection refused (Connection refused)”,而不包含具體的機器名、IP、埠等,一個重要考量就是信息安全。
類似的情況在日誌中也有,比如,用戶數據一般是不可以輸出到日誌裡面的。

受檢異常 or 非受檢異常

在函數拋出異常的時候,如果選擇向上拋出,我們應該選擇拋出哪種類型的異常呢?是受檢異常還是非受檢異常?

對於代碼 bug(比如下標訪問越界、空指針)以及不可恢復的異常(比如資料庫連接失敗),即便我們捕獲了,也做不了太多事情,我們希望程式能 fail-fast,所以,我們傾向於使用非受檢異常,將程式終止掉。
對於可恢復異常、業務異常,比如提現金額大於餘額的異常,我們更傾向於使用受檢異常,明確告知調用者需要捕獲處理。

處理異常的原則

儘量不要捕獲通用異常

儘量不要捕獲類似 Exception 這樣的通用異常,而應該捕獲特定異常(儘量縮小捕獲的異常範圍)。
下麵舉例說明,實例代碼如下:

try {
    // 業務代碼
    // …
    Thread.sleep(1000L);
} catch (Exception e) {
    // Ignore it
}

對於 Thread.sleep() 函數拋出的 InterruptedException,我們不應該捕獲 Exception 通用異常,而應該捕獲 InterruptedException 這樣的特定異常。


這是因為我們要保證程式不會捕獲到我們不希望捕獲的異常。比如,我們更希望 RuntimeException 導致線程終止,而不是被捕獲。

不要生吞異常

不要生吞(swallow)異常,儘量把異常信息記錄到日誌系統中。
這是異常處理中要特別註意的事情,因為生吞異常很可能會導致難以診斷的詭異情況。
如果我們沒有把異常拋出,也沒有把異常記錄到日誌系統,程式可能會在後續出現難以排查的 bug。沒人能夠輕易判斷究竟是哪裡拋出了異常,以及是什麼原因產生了異常。


再來看一段代碼

try {
    // 業務代碼
    // …
} catch (IOException e) {
    e.printStackTrace();
}

這段代碼作為一段實驗代碼,是沒有任何問題的,但是在產品代碼中,通常都不允許這樣處理。
你先思考一下這是為什麼呢?
我們先來看看 printStackTrace() 的文檔,開頭就是“Prints this throwable and its backtrace to the standard error stream”。問題就在這裡,在稍微複雜一點的生產系統中,標準出錯(STERR)不是個合適的輸出選項,因為你很難判到底輸出到哪裡去了。尤其是對於分散式系統,如果發生異常,但是無法找到堆棧軌跡(stacktrace),這純屬是為診斷設置障礙。
所以,最好使用產品日誌,詳細地將異常記錄到日誌系統里。

異常處理時,性能開銷大的地方

我們從性能角度來審視一下 Java 的異常處理機制,這裡有兩個性能開銷相對大的地方:

  • try-catch 代碼段會產生額外的性能開銷,或者換個角度說,它往往會影響 JVM 對代碼進行優化,所以建議僅捕獲有必要的代碼段,儘量不要一個大的 try 包住整段的代碼;
  • Java 每實例化一個 Exception,都會對當時的棧進行快照,這是一個相對比較重的操作。如果實例化 Exception 發生的非常頻繁,這個開銷可就不能被忽略了。

當我們的服務出現反應變慢、吞吐量下降的時候,檢查發生最頻繁的 Exception 也是一種思路。

參考文章

Exception和Error有什麼區別?
程式出錯該返回啥?NULL、異常、錯誤碼、空對象?

個人語雀

本文來自博客園,作者:真正的飛魚,轉載請註明原文鏈接:https://www.cnblogs.com/feiyu2/p/exception.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 看《C++ Primer Plus》時整理的學習筆記,部分內容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯。只做學習記錄用途。 ...
  • # 流程式控制制 學習目標: ~~~txt1. idea安裝與使用2. 流程式控制制if...else結構3. 流程式控制制switch結構4. 流程式控制制迴圈結構5. 流程式控制制關鍵字~~~ # 一、流程式控制制概述 什麼是流程式控制制? 流程式控制制是用來控製程序中各語句執行順序的語法。流程式控制制主要包含: * 順序結構 * ...
  • 一、字典、元組的多重嵌套 例 1:記錄全班學生的成績。 分析:定義一個 SimpleGradebook類, 學生名是字典self._grades的鍵,成績是字典self._grades的值。 class SimpleGradebook(): def __init__(self): self._gra ...
  • Django python網路編程回顧 之前我們介紹過web應用程式和http協議,簡單瞭解過web開發的概念。Web應用程式的本質 接收並解析HTTP請求,獲取具體的請求信息 處理本次HTTP請求,即完成本次請求的業務邏輯處理 構造並返回處理結果——HTTP響應 import socket ser ...
  • 三、SpringAMQP SpringAMQP是基於RabbitMQ封裝的一套模板,並且還利用SpringBoot對其實現了自動裝配,使用起來非常方便 SpringAMQP的官方地址 https://spring.io/projects/spring-amqp AMQP Spring AMQP Sp ...
  • 在本篇文章當中主要跟大家介紹併發的基礎知識,從最基本的問題出發層層深入,幫助大家瞭解併發知識,並且打好併發的基礎!!! ...
  • 1、應用場景 電商商城,商家上架了一個秒殺活動,早上10點開始,商品A參與秒殺,一共有20個庫存,預計10W的人去搶。 2、面臨問題 高併發、庫存不可超賣 3、問題解決 1)高併發,我們不能把所有的請求都去資料庫查商品詳情,查商品庫存,這樣資料庫會頂不住,很容易的我們就想到了用Redis解決; 2) ...
  • 歡迎關註公眾號:bin的技術小屋,本文圖片載入不出來的話可查看公眾號原文 本系列Netty源碼解析文章基於 4.1.56.Final版本 寫在前面..... 本文是筆者肉眼盯 Bug 系列的第三彈,前兩彈分別是: 抓到Netty一個Bug,順帶來透徹地聊一下Netty是如何高效接收網路連接的 ,在這 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...