Java異常處理和設計

来源:http://www.cnblogs.com/aishangJava/archive/2017/05/17/6866633.html
-Advertisement-
Play Games

在程式設計中,進行異常處理是非常關鍵和重要的一部分。一個程式的異常處理框架的好壞直接影響到整個項目的代碼質量以及後期維護成本和難度。試想一下,如果一個項目從頭到尾沒有考慮過異常處理,當程式出錯從哪裡尋找出錯的根源?但是如果一個項目異常處理設計地過多,又會嚴重影響到代碼質量以及程式的性能。因此,如何高... ...


學習Java的同學註意了!!! 
學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:618528494  我們一起學Java!

  在程式設計中,進行異常處理是非常關鍵和重要的一部分。一個程式的異常處理框架的好壞直接影響到整個項目的代碼質量以及後期維護成本和難度。試想一下,如果一個項目從頭到尾沒有考慮過異常處理,當程式出錯從哪裡尋找出錯的根源?但是如果一個項目異常處理設計地過多,又會嚴重影響到代碼質量以及程式的性能。因此,如何高效簡潔地設計異常處理是一門藝術,本文下麵先講述Java異常機制最基礎的知識,然後給出在進行Java異常處理設計時的幾個建議。

  若有不正之處,請多多諒解和指正,不勝感激。

  以下是本文的目錄大綱:

  一.什麼是異常
  二.Java中如何處理異常
  三.深刻理解try,catch,finally,throws,throw五個關鍵字
  四.在類繼承的時候,方法覆蓋時如何進行異常拋出聲明
  五.異常處理和設計的幾個建議

一.什麼是異常                                                                              

  異常的英文單詞是exception,字面翻譯就是“意外、例外”的意思,也就是非正常情況。事實上,異常本質上是程式上的錯誤,包括程式邏輯錯誤和系統錯誤。比如使用空的引用、數組下標越界、記憶體溢出錯誤等,這些都是意外的情況,背離我們程式本身的意圖。錯誤在我們編寫程式的過程中會經常發生,包括編譯期間和運行期間的錯誤,在編譯期間出現的錯誤有編譯器幫助我們一起修正,然而運行期間的錯誤便不是編譯器力所能及了,並且運行期間的錯誤往往是難以預料的。假若程式在運行期間出現了錯誤,如果置之不理,程式便會終止或直接導致系統崩潰,顯然這不是我們希望看到的結果。因此,如何對運行期間出現的錯誤進行處理和補救呢?Java提供了異常機制來進行處理,通過異常機制來處理程式運行期間出現的錯誤。通過異常機制,我們可以更好地提升程式的健壯性。

  在Java中異常被當做對象來處理,根類是java.lang.Throwable類,在Java中定義了很多異常類(如OutOfMemoryError、NullPointerException、IndexOutOfBoundsException等),這些異常類分為兩大類:Error和Exception。

  Error是無法處理的異常,比如OutOfMemoryError,一般發生這種異常,JVM會選擇終止程式。因此我們編寫程式時不需要關心這類異常。

  Exception,也就是我們經常見到的一些異常情況,比如NullPointerException、IndexOutOfBoundsException,這些異常是我們可以處理的異常。

  Exception類的異常包括checked exception和unchecked exception(unchecked exception也稱運行時異常RuntimeException,當然這裡的運行時異常並不是前面我所說的運行期間的異常,只是Java中用運行時異常這個術語來表示,Exception類的異常都是在運行期間發生的)。

  unchecked exception(非檢查異常),也稱運行時異常(RuntimeException),比如常見的NullPointerException、IndexOutOfBoundsException。對於運行時異常,java編譯器不要求必須進行異常捕獲處理或者拋出聲明,由程式員自行決定。

  checked exception(檢查異常),也稱非運行時異常(運行時異常以外的異常就是非運行時異常),java編譯器強製程序員必須進行捕獲處理,比如常見的IOExeption和SQLException。對於非運行時異常如果不進行捕獲或者拋出聲明處理,編譯都不會通過。

  在Java中,異常類的結構層次圖如下圖所示:

  

  在Java中,所有異常類的父類是Throwable類,Error類是error類型異常的父類,Exception類是exception類型異常的父類,RuntimeException類是所有運行時異常的父類,RuntimeException以外的並且繼承Exception的類是非運行時異常。

  典型的RuntimeException包括NullPointerException、IndexOutOfBoundsException、IllegalArgumentException等。

  典型的非RuntimeException包括IOException、SQLException等。

二.Java中如何處理異常                                                                 

   在Java中如果需要處理異常,必須先對異常進行捕獲,然後再對異常情況進行處理。如何對可能發生異常的代碼進行異常捕獲和處理呢?使用try和catch關鍵字即可,如下麵一段代碼所示:

try {
  File file = new File("d:/a.txt");
  if(!file.exists())
    file.createNewFile();
} catch (IOException e) {
  // TODO: handle exception
}

  被try塊包圍的代碼說明這段代碼可能會發生異常,一旦發生異常,異常便會被catch捕獲到,然後需要在catch塊中進行異常處理。

  這是一種處理異常的方式。在Java中還提供了另一種異常處理方式即拋出異常,顧名思義,也就是說一旦發生異常,我把這個異常拋出去,讓調用者去進行處理,自己不進行具體的處理,此時需要用到throw和throws關鍵字。 

  下麵看一個示例:

public class Main {
    public static void main(String[] args) {
        try {
            createFile();
        } catch (Exception e) {
            // TODO: handle exception
        }
    }
     
    public static void createFile() throws IOException{
        File file = new File("d:/a.txt");
        if(!file.exists())
            file.createNewFile();
    }
}

  這段代碼和上面一段代碼的區別是,在實際的createFile方法中並沒有捕獲異常,而是用throws關鍵字聲明拋出異常,即告知這個方法的調用者此方法可能會拋出IOException。那麼在main方法中調用createFile方法的時候,採用try...catch塊進行了異常捕獲處理。

  當然還可以採用throw關鍵字手動來拋出異常對象。下麵看一個例子:

public class Main {
    public static void main(String[] args) {
        try {
            int[] data = new int[]{1,2,3};
            System.out.println(getDataByIndex(-1,data));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
         
    }
     
    public static int getDataByIndex(int index,int[] data) {
        if(index<0||index>=data.length)
            throw new ArrayIndexOutOfBoundsException("數組下標越界");
        return data[index];
    }
}

  然後在catch塊中進行捕獲。

  也就說在Java中進行異常處理的話,對於可能會發生異常的代碼,可以選擇三種方法來進行異常處理:

  1)對代碼塊用try..catch進行異常捕獲處理;

  2)在 該代碼的方法體外用throws進行拋出聲明,告知此方法的調用者這段代碼可能會出現這些異常,你需要謹慎處理。此時有兩種情況:

    如果聲明拋出的異常是非運行時異常,此方法的調用者必須顯示地用try..catch塊進行捕獲或者繼續向上層拋出異常。

    如果聲明拋出的異常是運行時異常,此方法的調用者可以選擇地進行異常捕獲處理。

  3)在代碼塊用throw手動拋出一個異常對象,此時也有兩種情況,跟2)中的類似:

    如果拋出的異常對象是非運行時異常,此方法的調用者必須顯示地用try..catch塊進行捕獲或者繼續向上層拋出異常。

    如果拋出的異常對象是運行時異常,此方法的調用者可以選擇地進行異常捕獲處理。

  (如果最終將異常拋給main方法,則相當於交給jvm自動處理,此時jvm會簡單地列印異常信息)

三.深刻理解try,catch,finally,throws,throw五個關鍵字                   

   下麵我們來看一下異常機制中五個關鍵字的用法以及需要註意的地方。

1.try,catch,finally

  try關鍵字用來包圍可能會出現異常的邏輯代碼,它單獨無法使用,必須配合catch或者finally使用。Java編譯器允許的組合使用形式只有以下三種形式:

  try...catch...;       try....finally......;    try....catch...finally...

  當然catch塊可以有多個,註意try塊只能有一個,finally塊是可選的(但是最多只能有一個finally塊)。

  三個塊執行的順序為try—>catch—>finally。

  當然如果沒有發生異常,則catch塊不會執行。但是finally塊無論在什麼情況下都是會執行的(這點要非常註意,因此部分情況下,都會將釋放資源的操作放在finally塊中進行)。

  在有多個catch塊的時候,是按照catch塊的先後順序進行匹配的,一旦異常類型被一個catch塊匹配,則不會與後面的catch塊進行匹配。

  在使用try..catch..finally塊的時候,註意千萬不要在finally塊中使用return,因為finally中的return會覆蓋已有的返回值。下麵看一個例子:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
 
 
public class Main {
    public static void main(String[] args) {
        String str = new Main().openFile();
        System.out.println(str);
         
    }
     
    public String openFile() {
        try {
            FileInputStream inputStream = new FileInputStream("d:/a.txt");
            int ch = inputStream.read();
            System.out.println("aaa");
            return "step1";
        } catch (FileNotFoundException e) {
            System.out.println("file not found");
            return "step2";
        }catch (IOException e) {
            System.out.println("io exception");
            return "step3";
        }finally{
            System.out.println("finally block");
            //return "finally";
        }
    }
}

  這段程式的輸出結果為:

  可以看出,在try塊中發生FileNotFoundException之後,就跳到第一個catch塊,列印"file not found"信息,並將"step2"賦值給返回值,然後執行finally塊,最後將返回值返回。

  從這個例子說明,無論try塊或者catch塊中是否包含return語句,都會執行finally塊。

  如果將這個程式稍微修改一下,將finally塊中的return語句註釋去掉,運行結果是:

  

  最後列印出的是"finally",返回值被重新覆蓋了。

  因此如果方法有返回值,切忌不要再finally中使用return,這樣會使得程式結構變得混亂。

 2.throws和thow關鍵字

  1)throws出現在方法的聲明中,表示該方法可能會拋出的異常,然後交給上層調用它的方法程式處理,允許throws後面跟著多個異常類型;

  2)一般會用於程式出現某種邏輯時程式員主動拋出某種特定類型的異常。throw只會出現在方法體中,當方法在執行過程中遇到異常情況時,將異常信息封裝為異常對象,然後throw出去。throw關鍵字的一個非常重要的作用就是 異常類型的轉換(會在後面闡述道)。

  throws表示出現異常的一種可能性,並不一定會發生這些異常;throw則是拋出了異常,執行throw則一定拋出了某種異常對象。兩者都是消極處理異常的方式(這裡的消極並不是說這種方式不好),只是拋出或者可能拋出異常,但是不會由方法去處理異常,真正的處理異常由此方法的上層調用處理。

四.在類繼承的時候,方法覆蓋時如何進行異常拋出聲明                        

   本小節討論子類重寫父類方法的時候,如何確定異常拋出聲明的類型。下麵是三點原則:

  1)父類的方法沒有聲明異常,子類在重寫該方法的時候不能聲明異常;

  2)如果父類的方法聲明一個異常exception1,則子類在重寫該方法的時候聲明的異常不能是exception1的父類;

  3)如果父類的方法聲明的異常類型只有非運行時異常(運行時異常),則子類在重寫該方法的時候聲明的異常也只能有非運行時異常(運行時異常),不能含有運行時異常(非運行時異常)。

  

五.異常處理和設計的幾個建議                                                         

   以下是根據前人總結的一些異常處理的建議:

1.只在必要使用異常的地方纔使用異常,不要用異常去控製程序的流程

  謹慎地使用異常,異常捕獲的代價非常高昂,異常使用過多會嚴重影響程式的性能。如果在程式中能夠用if語句和Boolean變數來進行邏輯判斷,那麼儘量減少異常的使用,從而避免不必要的異常捕獲和處理。比如下麵這段經典的程式:

public void useExceptionsForFlowControl() {  
  try {  
  while (true) {  
    increaseCount();  
    }  
  } catch (MaximumCountReachedException ex) {  
  }  
  //Continue execution  
}  
    
public void increaseCount() throws MaximumCountReachedException {  
  if (count >= 5000)  
    throw new MaximumCountReachedException();  
}

  上邊的useExceptionsForFlowControl()用一個無限迴圈來增加count直到拋出異常,這種做法並沒有說讓代碼不易讀,而是使得程式執行效率降低。

2.切忌使用空catch塊

  在捕獲了異常之後什麼都不做,相當於忽略了這個異常。千萬不要使用空的catch塊,空的catch塊意味著你在程式中隱藏了錯誤和異常,並且很可能導致程式出現不可控的執行結果。如果你非常肯定捕獲到的異常不會以任何方式對程式造成影響,最好用Log日誌將該異常進行記錄,以便日後方便更新和維護。

3.檢查異常和非檢查異常的選擇

  一旦你決定拋出異常,你就要決定拋出什麼異常。這裡面的主要問題就是拋出檢查異常還是非檢查異常。

  檢查異常導致了太多的try…catch代碼,可能有很多檢查異常對開發人員來說是無法合理地進行處理的,比如SQLException,而開發人員卻不得不去進行try…catch,這樣就會導致經常出現這樣一種情況:邏輯代碼只有很少的幾行,而進行異常捕獲和處理的代碼卻有很多行。這樣不僅導致邏輯代碼閱讀起來晦澀難懂,而且降低了程式的性能。

  我個人建議儘量避免檢查異常的使用,如果確實該異常情況的出現很普遍,需要提醒調用者註意處理的話,就使用檢查異常;否則使用非檢查異常。

  因此,在一般情況下,我覺得儘量將檢查異常轉變為非檢查異常交給上層處理。

4.註意catch塊的順序

  不要把上層類的異常放在最前面的catch塊。比如下麵這段代碼:

try {
        FileInputStream inputStream = new FileInputStream("d:/a.txt");
        int ch = inputStream.read();
        System.out.println("aaa");
        return "step1";
    } catch (IOException e) {
        System.out.println("io exception");        
         return "step2";
    }catch (FileNotFoundException e) {
        System.out.println("file not found");          
        return "step3";
    }finally{
        System.out.println("finally block");
        //return "finally";
    }

  第二個catch的FileNotFoundException將永遠不會被捕獲到,因為FileNotFoundException是IOException的子類。

5.不要將提供給用戶看的信息放在異常信息里

  比如下麵這段代碼:

public class Main {
    public static void main(String[] args) {
        try {
            String user = null;
            String pwd = null;
            login(user,pwd);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
         
    }
     
    public static void login(String user,String pwd) {
        if(user==null||pwd==null)
            throw new NullPointerException("用戶名或者密碼為空");
        //...
    }
}

  展示給用戶錯誤提示信息最好不要跟程式混淆一起,比較好的方式是將所有錯誤提示信息放在一個配置文件中統一管理。

6.避免多次在日誌信息中記錄同一個異常

  只在異常最開始發生的地方進行日誌信息記錄。很多情況下異常都是層層向上跑出的,如果在每次向上拋出的時候,都Log到日誌系統中,則會導致無從查找異常發生的根源。

7. 異常處理儘量放在高層進行

  儘量將異常統一拋給上層調用者,由上層調用者統一之時如何進行處理。如果在每個出現異常的地方都直接進行處理,會導致程式異常處理流程混亂,不利於後期維護和異常錯誤排查。由上層統一進行處理會使得整個程式的流程清晰易懂。

8. 在finally中釋放資源

  如果有使用文件讀取、網路操作以及資料庫操作等,記得在finally中釋放資源。這樣不僅會使得程式占用更少的資源,也會避免不必要的由於資源未釋放而發生的異常情況。 

學習Java的同學註意了!!! 
學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:618528494  我們一起學Java!


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

-Advertisement-
Play Games
更多相關文章
  • 最近做WInfrom項目,對錶格和控制項的數據綁定非常喜歡用實體類對象來解決,但是綁定以後 又怎麼從控制項中拿到實體類或者轉換為datatable 或者dataset呢 經過在網上的搜索以及自己的改進 完成了一個轉換類,分享給大家。 已經用在項目中,使用時沒有問題的,如果有缺陷請大家指正。 ...
  • 前言 在asp.net core中,我巨硬引入了DI容器,我們可以在不使用第三方插件的情況下輕鬆實現依賴註入。如下代碼: 1 // This method gets called by the runtime. Use this method to add services to the conta ...
  • 新建一個空的Web項目 然後在Nuget庫中安裝下麵兩個包 Nancy Nancy.Hosting.Aspnet 然後在根目錄添加三個文件夾,分別是models,Module,Views 然後往Module文件夾裡面添加ConstraintRoutingModule類 然後往Models文件夾裡面添 ...
  • 之前公司項目參考 NopCommerce 開發了一套系統,但是不支持 UnitOfWork,最近想開發新的項目,所以就基於原有的基礎上又添加 UnitOfWork 支持,由於目前正在逐步完善中,所以可能存在各種問題,這裡發出來僅供大家參考 ...
  • 5.1 編程語言的基元類型 c 不管在什麼操作系統上運行,int始終映射到System.Int32; long始終映射到System.Int64 可以通過checked/unchecked操作符/語句打開或關閉溢出檢查,如: 在checked操作符或語句中調用方法,不會對該方法造成任何影響,如: 盡 ...
  • 1.問題描述:嘗試載入 Oracle 客戶端庫時引發 BadImageFormatException。如果在安裝 32 位 Oracle 客戶端組件的情況下以 64 位模式運行,將出現此問題。 解決方法:這主要是因為安裝的 Oracle 客戶端版本問題,版本有32bit和64bit的。如果我們的VS ...
  • 策略模式按我個人的理解說就是將類中重覆使用的代碼分離出來形成一個策略類,其他類想要調用的話,首先在初始化的時候就要把這個策略類作為參數傳遞進來即形成一種組合關係,然後在類內部就可以直接調用這個策略類中的邏輯了。 簡單說一下類的組合與聚合關係(很長一段時間里對這種關係一直比較懵,代碼看多了,並查看了一 ...
  • Java記憶體管理 一.記憶體分類 分為如下四類: 堆區(Heap) 棧區(Stack) 數據區(Data segment) 代碼區(Code segment) 二.作用 代碼區(Code segment):存放程式的代碼 數據區(Data segment):存放靜態變數和字元串常量 棧區(Stack) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...