淺談java異常[Exception]

来源:http://www.cnblogs.com/aishangJava/archive/2017/01/04/6249643.html
-Advertisement-
Play Games

一. 異常的定義 在《java編程思想》中這樣定義 異常:阻止當前方法或作用域繼續執行的問題。雖然java中有異常處理機制,但是要明確一點,決不應該用"正常"的態度來看待異常。絕對一點說異常就是某種意義上的錯誤,就是問題,它可能會導致程式失敗。之所以java要提出異常處理機制,就是要告訴開發人員,... ...




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

一. 異常的定義

在《java編程思想》中這樣定義 異常:阻止當前方法或作用域繼續執行的問題。雖然java中有異常處理機制,但是要明確一點,決不應該用"正常"的態度來看待異常。絕對一點說異常就是某種意義上的錯誤,就是問題,它可能會導致程式失敗。之所以java要提出異常處理機制,就是要告訴開發人員,你的程式出現了不正常的情況,請註意。

記得當初學習java的時候,異常總是搞不太清楚,不知道這個異常是什麼意思,為什麼會有這個機制?但是隨著知識的積累逐漸也對異常有一點感覺了。舉一個例子來說明一下異常的用途。

 

public class Calculator {     public int devide(int num1, int num2) {         //判斷除數是否為0         if(num2 == 0) {             throw new IllegalArgumentException("除數不能為零");         }                   return num1/num2;     } }

 

看一下這個類中關於除運算的方法,如果你是新手你可能會直接返回計算結果,根本不去考慮什麼參數是否正確,是否合法(當然可以原諒,誰都是這樣過來的)。但是我們應儘可能的考慮周全,把可能導致程式失敗的"苗頭"扼殺在搖籃中,所以進行參數的合法性檢查就很有必要了。其中執行參數檢查拋出來的那個參數非法異常,這就屬於這個方法的不正常情況。正常情況下我們會正確的使用計算器,但是不排除粗心大意把除數賦值為0。如果你之前沒有考慮到這種情況,並且恰巧用戶數學基礎不好,那麼你完了。但是如果你之前考慮到了這種情況,那麼很顯然錯誤已在你的掌控之中。

 

二. 異常掃盲行動

 

今天和別人聊天時看到一個笑話:世界上最真情的相依,是你在try我在catch。無論你發神馬脾氣,我都默默承受,靜靜處理。 大多數新手對java異常的感覺就是:try...catch...。沒錯,這是用的最多的,也是最實用的。我的感覺就是:java異常是從"try...catch..."走來。

 

首先來熟悉一下java的異常體系:

 

Throwable 類是 Java 語言中所有錯誤或異常的超類(這就是一切皆可拋的東西)。它有兩個子類:Error和Exception。

 

Error:用於指示合理的應用程式不應該試圖捕獲的嚴重問題。這種情況是很大的問題,大到你不能處理了,所以聽之任之就行了,你不用管它。比如說VirtualMachineError:當 Java 虛擬機崩潰或用盡了它繼續操作所需的資源時,拋出該錯誤。好吧,就算這個異常的存在了,那麼應該何時,如何處理它呢??交給JVM吧,沒有比它更專業的了。

 

Exception:它指出了合理的應用程式想要捕獲的條件。Exception又分為兩類:一種是CheckedException,一種是UncheckedException。這兩種Exception的區別主要是CheckedException需要用try...catch...顯示的捕獲,而UncheckedException不需要捕獲。通常UncheckedException又叫做RuntimeException。《effective java》指出:對於可恢復的條件使用被檢查的異常(CheckedException),對於程式錯誤(言外之意不可恢復,大錯已經釀成)使用運行時異常(RuntimeException)。

 

我們常見的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。對於那些CheckedException就不勝枚舉了,我們在編寫程式過程中try...catch...捕捉的異常都是CheckedException。io包中的IOException及其子類,這些都是CheckedException。

 

 

 

三. 異常的使用

 

在異常的使用這一部分主要是演示代碼,都是我們平常寫代碼的過程中會遇到的(當然只是一小部分),拋磚引玉嗎!

 

例1. 這個例子主要通過兩個方法對比來演示一下有了異常以後代碼的執行流程。 

 

public static void testException1() {         int[] ints = new int[] { 1, 2, 3, 4 };         System.out.println("異常出現前");         try {             System.out.println(ints[4]);             System.out.println("我還有幸執行到嗎");// 發生異常以後,後面的代碼不能被執行         } catch (IndexOutOfBoundsException e) {             System.out.println("數組越界錯誤");         }         System.out.println("異常出現後");     }     /*output:     異常出現前     數組越界錯誤     4     異常出現後     */

 

  

public static void testException2() {         int[] ints = new int[] { 1, 2, 3, 4 };         System.out.println("異常出現前");         System.out.println(ints[4]);         System.out.println("我還有幸執行到嗎");// 發生異常以後,他後面的代碼不能被執行     }

  

首先指出例子中的不足之處,IndexOutofBoundsException是一個非受檢異常,所以不用try...catch...顯示捕捉,但是我的目的是對同一個異常用不同的處理方式,看它會有什麼不同的而結果(這裡也就只能用它將就一下了)。異常出現時第一個方法只是跳出了try塊,但是它後面的代碼會照樣執行的。但是第二種就不一樣了直接跳出了方法,比較強硬。從第一個方法中我們看到,try...catch...是一種"事務性"的保障,它的目的是保證程式在異常的情況下運行完畢,同時它還會告知程式員程式中出錯的詳細信息(這種詳細信息有時要依賴於程式員設計)。

例2. 重新拋出異常

 

public class Rethrow {     public static void readFile(String file) throws FileNotFoundException {         try {             BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));         } catch (FileNotFoundException e) {             e.printStackTrace();             System.err.println("不知道如何處理該異常或者根本不想處理它,但是不做處理又不合適,這是重新拋出異常交給上一級處理");             //重新拋出異常             throw e;         }     }           public static void printFile(String file) {         try {             readFile(file);         } catch (FileNotFoundException e) {             e.printStackTrace();         }     }           public static void main(String[] args) {         printFile("D:/file");     } }

 

  

異常的本意是好的,讓我們試圖修複程式,但是現實中我們修複的幾率很小,我們很多時候就是用它來記錄出錯的信息。如果你厭倦了不停的處理異常,重新拋出異常對你來說可能是一個很好的解脫。原封不動的把這個異常拋給上一級,拋給調用這個方法的人,讓他來費腦筋吧。這樣看來,java異常(當然指的是受檢異常)又給我們平添很多麻煩,儘管它的出發點是好的。

 

例3. 異常鏈的使用及異常丟失

定義三個異常類:ExceptionA,ExceptionB,ExceptionC

 

public class ExceptionA extends Exception {     public ExceptionA(String str) {         super();     } }   public class ExceptionB extends ExceptionA {       public ExceptionB(String str) {         super(str);     } }   public class ExceptionC extends ExceptionA {     public ExceptionC(String str) {         super(str);     } }

異常丟失的情況:

 

public class NeverCaught {     static void f() throws ExceptionB{         throw new ExceptionB("exception b");     }       static void g() throws ExceptionC {         try {             f();         } catch (ExceptionB e) {             ExceptionC c = new ExceptionC("exception a");             throw c;         }     }       public static void main(String[] args) {             try {                 g();             } catch (ExceptionC e) {                 e.printStackTrace();             }     }   } /* exception.ExceptionC at exception.NeverCaught.g(NeverCaught.java:12) at exception.NeverCaught.main(NeverCaught.java:19) */

為什麼只是列印出來了ExceptionC而沒有列印出ExceptionB呢?這個還是自己分析一下吧!

上面的情況相當於少了一種異常,這在我們排錯的過程中非常的不利。那我們遇到上面的情況應該怎麼辦呢?這就是異常鏈的用武之地:保存異常信息,在拋出另外一個異常的同時不丟失原來的異常。

 

public class NeverCaught {     static void f() throws ExceptionB{         throw new ExceptionB("exception b");     }       static void g() throws ExceptionC {         try {             f();         } catch (ExceptionB e) {             ExceptionC c = new ExceptionC("exception a");             //異常連             c.initCause(e);             throw c;         }     }       public static void main(String[] args) {             try {                 g();             } catch (ExceptionC e) {                 e.printStackTrace();             }     }   } /* exception.ExceptionC at exception.NeverCaught.g(NeverCaught.java:12) at exception.NeverCaught.main(NeverCaught.java:21) Caused by: exception.ExceptionB at exception.NeverCaught.f(NeverCaught.java:5) at exception.NeverCaught.g(NeverCaught.java:10) ... 1 more */

這個異常鏈的特性是所有異常均具備的,因為這個initCause()方法是從Throwable繼承的。

例4. 清理工作

清理工作對於我們來說是必不可少的,因為如果一些消耗資源的操作,比如IO,JDBC。如果我們用完以後沒有及時正確的關閉,那後果會很嚴重,這意味著記憶體泄露。異常的出現要求我們必須設計一種機制不論什麼情況下,資源都能及時正確的清理。這就是finally。

 

public void readFile(String file) {         BufferedReader reader = null;         try {             reader = new BufferedReader(new InputStreamReader(                     new FileInputStream(file)));             // do some other work         } catch (FileNotFoundException e) {             e.printStackTrace();         } finally {             try {                 reader.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }

例子非常的簡單,是一個讀取文件的例子。這樣的例子在JDBC操作中也非常的常見。(所以,我覺得對於資源的及時正確清理是一個程式員的基本素質之一。)

Try...finally結構也是保證資源正確關閉的一個手段。如果你不清楚代碼執行過程中會發生什麼異常情況會導致資源不能得到清理,那麼你就用try對這段"可疑"代碼進行包裝,然後在finally中進行資源的清理。舉一個例子:

 

public void readFile() {         BufferedReader reader = null;         try {             reader = new BufferedReader(new InputStreamReader(                     new FileInputStream("file")));             // do some other work                       //close reader             reader.close();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }     }

 

我們註意一下這個方法和上一個方法的區別,下一個人可能習慣更好一點,及早的關閉reader。但是往往事與願違,因為在reader.close()以前異常隨時可能發生,這樣的代碼結構不能預防任何異常的出現。因為程式會在異常出現的地方跳出,後面的代碼不能執行(這在上面應經用實例證明過)。這時我們就可以用try...finally來改造:

 

public void readFile() {         BufferedReader reader = null;         try {             try {                 reader = new BufferedReader(new InputStreamReader(                         new FileInputStream("file")));                 // do some other work                   // close reader             } finally {                 reader.close();             }         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }     }

及早的關閉資源是一種良好的行為,因為時間越長你忘記關閉的可能性越大。這樣在配合上try...finally就保證萬無一失了(不要嫌麻煩,java就是這麼中規中矩)。

再說一種情況,假如我想在構造方法中打開一個文件或者創建一個JDBC連接,因為我們要在其他的方法中使用這個資源,所以不能在構造方法中及早的將這個資源關閉。那我們是不是就沒轍了呢?答案是否定的。看一下下麵的例子:

 

public class ResourceInConstructor {     BufferedReader reader = null;     public ResourceInConstructor() {         try {             reader = new BufferedReader(new InputStreamReader(new FileInputStream("")));         } catch (FileNotFoundException e) {             e.printStackTrace();         }     }           public void readFile() {         try {             while(reader.readLine()!=null) {                 //do some work             }         } catch (IOException e) {             e.printStackTrace();         }     }           public void dispose() {         try {             reader.close();         } catch (IOException e) {             e.printStackTrace();         }     } }

這一部分講的多了一點,但是異常確實是看起來容易用起來難的東西呀,java中還是有好多的東西需要深挖的。

 

四. 異常的誤用

對於異常的誤用著實很常見,上一部分中已經列舉了幾個,大家仔細的看一下。下麵再說兩個其他的。

例1. 用一個Exception來捕捉所有的異常,頗有"一夫當關萬夫莫開"的氣魄。不過這也是最傻的行為。

 

public void readFile(String file) {         BufferedReader reader = null;         Connection conn = null;         try {             reader = new BufferedReader(new InputStreamReader(                     new FileInputStream(file)));             // do some other work                           conn = DriverManager.getConnection("");             //...         } catch (Exception e) {             e.printStackTrace();         } finally {             try {                 reader.close();                 conn.close();             } catch (Exception e) {                 e.printStackTrace();             }         }     }

 

  

從異常角度來說這樣嚴格的程式確實是萬無一失,所有的異常都能捕獲。但是站在編程人員的角度,萬一這個程式出錯了我們該如何分辨是到底是那引起的呢,IO還是JDBC...所以,這種寫法很值得當做一個反例。大家不要以為這種做法很幼稚,傻子才會做。我在公司實習時確實看見了類似的情況:只不過是人家沒有用Exception而是用了Throwable。

例2. 這裡就不舉例子了,上面的程式都是反例。異常是程式處理意外情況的機制,當程式發生意外時,我們需要儘可能多的得到意外的信息,包括發生的位置,描述,原因等等。這些都是我們解決問題的線索。但是上面的例子都只是簡單的printStackTrace()。如果我們自己寫代碼,就要儘可能多的對這個異常進行描述。比如說為什麼會出現這個異常,什麼情況下會發生這個異常。如果傳入方法的參數不正確,告知什麼樣的參數是合法的參數,或者給出一個sample。

例3. 將try block寫的簡短,不要所有的東西都扔在這裡,我們儘可能的分析出到底哪幾行程式可能出現異常,只是對可能出現異常的代碼進行try。儘量為每一個異常寫一個try...catch,避免異常丟失。在IO操作中,一個IOException也具有"一夫當關萬夫莫開"的氣魄。

五.總結

總結非常簡單,不要為了使用異常而使用異常。異常是程式設計的一部分,對它的設計也要考究點。

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

 


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

-Advertisement-
Play Games
更多相關文章
  • 參考 http://www.cnblogs.com/chengmo/archive/2010/10/02/1841355.html 問題:bash怎麼提取字元串的最後一位?例如python中string[-1]就是python字元串最後一位。 echo ${PATH:((${#PATH} - 1)) ...
  • RSA.h #ifndef _RSA_H #define _RSA_H #include #include #include /* 密鑰產生: 1.隨機選定兩個大素數p, q. 2.計算公鑰和私鑰的公共模數 n = pq . 3.計算模數n的歐拉函數 φ(n) . 4.選定一個正整數e, 使1 e,... ...
  • SHA-1.cpp TEST.cpp ...
  • if __name__== "__main__" 的意思(作用)python代碼復用 轉自:大步's Blog http://www.dabu.info/if-__-namelxx_-main__-mean-function-python-code-reuse.html 有人在學習python腳本時 ...
  • Java是最早開始有併發的語言之一,再過去傳統多任務的模式下,人們發現很難解決一些更為複雜的問題,這個時候我們就有了併發. 引用 & 160; & 160; & 160; & 160;多線程比多任務更加有挑戰。多線程是在同一個程式內部並行執行,因此會對相同的記憶體空間進行併發讀寫操作。這可能是在單線程 ...
  • 本文由博主原創,轉載請註明出處:我的博客-知乎爬蟲之爬蟲流程設計 git爬蟲項目地址(關註和star在哪裡~~):https://github.com/MatrixSeven/ZhihuSpider (已完結) 附贈之前爬取的數據一份(mysql): 鏈接:https://github.com/Ma ...
  • 要知道什麼是智能指針,首先瞭解什麼稱為 “資源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“資源分配即初始化” 在《C++ Primer》這樣解釋的,“通過定義一個類來封裝資源的分配和釋放,可以保證正確釋放資源” 核心 ...
  • 結果: ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...