[Java異常處理手冊和最佳實踐](http://blog.csdn.net/hp910315/article/details/49305225)
[TOC=1,3]
Java異常處理框架是非常強大并且很容易理解和使用,異常可能產生于不同的情況,例如:用戶錯誤數據的輸入,硬件故障,網絡連接失敗,數據服務器故障等等,下面我們需要學習在java中如何處理這些異常。
在程序執行的時候,無論什么時候產生錯誤,都會創建一個Exception對象并且正常的程序流也會中斷,JRE會嘗試找到處理這個異常的處理者。一個異常對象包含了許多的調試信息,例如:方法層次、產生異常的行號、異常類型等等。當異常在一個方法中產生,創建這個異常對象并且將它提交給運行環境的過程叫做拋出異常。
一旦運行環境接收到這個異常對象,它會嘗試找到這個異常的處理者,這個處理者就是一個處理這個異常的代碼塊,尋找異常處理者的過程是非常簡單的:它首先會搜索這個產生異常的方法,如果沒有合適的異常處理者,它就會查找這個方法的調用者,依次推進。所以,如果方法調用棧是A–>B–>C,如果異常在方法C中產生,那么搜索異常處理者的過程就是C–>B–>A,如果合適的異常處理者被找到,就會把這個異常對象傳遞給這個異常處理者進行處理,如果沒有找到合適的異常處理者,異常信息就會在應用終端打印處理。
在java程序中,我們會使用指定的關鍵字去創建一個異常處理塊,下面我們會講到。
**異常處理關鍵字**
為了能夠對異常進行處理,java里面提供了指定的關鍵字。
1、throw?
throw是為了拋出異常給java運行環境,讓它進行處理
2、throws?
如果在一個方法中產生了一個異常并且我們不想對其進行處理,我們就在方法上使用throws關鍵字,目的是為了讓這個方法的調用者知道這個方法可能產生異常,這個方法的調用者可以處理這個異常也可以使用同樣的方法來告訴上層的調用者。
3、try-catch?
try-catch是處理異常的代碼塊。
4、finally?
finally塊只能跟try-catch塊一塊使用,由于異常中斷了程序的執行,這樣我們有一些資源可能被打開了但是還沒來得及關閉,這樣就可以使用finally塊,不管會不會產生異常finally塊都會被執行。
下面來舉個例子來看看異常的處理。
~~~
package com.journaldev.exceptions;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionHandling {
public static void main(String[] args) throws FileNotFoundException, IOException {
try {
testException(-5);
testException(-10);
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally {
System.out.println("Releasing resources");
}
testException(15);
}
public static void testException(int i) throws FileNotFoundException, IOException{
if(i < 0) {
FileNotFoundException myException = new FileNotFoundException("Negative Integer "+i);
throw myException;
} else if(i > 10){
throw new IOException("Only supported for index 0 to 10");
}
}
}
~~~
程序輸出如下:
~~~
java.io.FileNotFoundException: Negative Integer -5
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
~~~
testException(-5)執行后,產生FileNotFoundException異常,程序流被中斷,這樣try塊后面的語句testException(-10)就不會被執行,因為不管是否產生異常,finally塊總會被執行,所以打印出了Releasing resources,另外,我們也可以看到finally塊后面的語句testException(15)會被繼續執行,它拋出了異常,但是沒有被捕捉,而是使用throws語句,交給了它的調用者。另外,printStackTrace()是Exception中一個非常有用的方法,在調試的時候可以使用。
下面對上面的內容進行了一些總結:?
1、如果沒有try塊,就不可能有catch和finally塊。?
2、try塊的后面應該有catch塊或者finally塊,也可以同時存在。?
3、在try-catch-finally塊之間,不能寫任何代碼。?
4、一個try塊的后面可以有多個catch塊。?
5、try-catch可以內部進行包含,類似于if-else語句,if-else內部可以包含if-else。?
6、一個try-catch塊的后面只能有一個finally塊。
**Exception層次**
我們知道,當產生任何的異常都會創建一個異常對象,Java異常是有層次的,它們使用繼承關系來對不同類型的異常進行分類。Throwable是整個繼承層次的父類,它有兩個子對象:Error和Exception,其中Exceptions又分為可檢查異常(checked exceptions)和運行時異常(runtime exception)。
1、Errors:?
他表示異常的發生場景超出了應用范圍,它無法預測也無法修復,例如,硬件故障、JVM崩潰或者內存溢出。這也是為什么我們有一個單獨的errors層次,我們不應該對其進行處理,一些普通的錯誤(Errors)包括內存異常(OutOfMemoryError)和棧溢出(StackOverflowError)。
2、Checked Exceptions:這種異常是我們程序可以預測并且進行修復的,例如:FileNotFoundException。我們應該捕獲這種異常并且給用戶提供有用信息,另外為了方便調試,可以適當的打印出錯誤信息。Exception是所有可檢查異常(Checked Exceptions)的父類,如果我們拋出了一個可檢查的異常,我們必須在這個方法中對其進行捕獲或者使用throws關鍵字把它拋給調用者。
3、Runtime Exception:運行時異常是程序員在編寫代碼時不好的編碼習慣或者錯誤的使用造成的,例如從數組中取元素的時候,我們應該首先檢查數組的長度,然后再去進行數組元素的提取,因為在運行時可能拋出ArrayIndexOutOfBoundException數組訪問越界異常。運行時異常可以不需要進行捕獲,也不需要在方法上添加throws關鍵字來標識,因為如果你有良好的編碼習慣,這些異常是可以避免的。
**Exception中有用的方法**
Exception和所有它的子類并沒有提供具體的方法,所有的方法是是定義在它的基類Throwable里面。異常根據不同的異常類型進行創建,這樣我們就可以很容易的找出根本原因并且通過這些類型處理異常。為了交互的目的,Throwable實現了Serializable接口。
下面列出了Throwable里面的一些有用的方法:
public String getMessage()
這個方法返回Throwable的消息字符串,當通過構造者創建異常對象的時候會提供這個消息字符串,我們自己在創建異常的時候,會傳入一個字符串,這個就是消息。
public String getLocalizedMessage()?
子類可以重寫這個方法,為調用它的程序提供指定的消息。Throwable實現這個方法僅僅通過getMessage()去返回異常消息。
public synchronized Throwable getCause()?
這個方法返回異常的原因,如果是空id就說明這個異常未知。
public String toString()?
它將Throwable以字符串的形式返回,主要包括Throwable類名和本地消息。
public void printStackTrace()?
這種方法打印堆棧跟蹤信息到標準錯誤流,這種方法重載,我們可以傳遞的PrintStream或PrintWriter的作為參數以文件或流的形式打印堆棧跟蹤信息。
**Java 7自動資源管理和Catch塊的完善**
如果你在一個單獨的try塊中捕獲許多的異常,那個這個catch塊的代碼可能會看起來丑陋并且包含了許多額外的代碼去打印錯誤,在java 7中,在一個單獨的catch塊中可以捕獲許多異常,例如:
~~~
catch(IOException | SQLException | Exception ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}
~~~
具體的用法可以看看:[Java 7 Catch Block Improvements](http://www.journaldev.com/629/catching-multiple-exceptions-in-single-catch-and-rethrowing-exceptions-with-improved-type-checking-java-7-feature)
許多時候,我們使用finally塊來關閉資源,有時我們如果忘記了關閉它們,這樣當資源被耗盡的時候可能就會出現運行時異常。這種異常很難進行調試,我們需要檢查所有使用這種資源類型的地方,確保是否關閉。在Java 7中,我們創建一個資源在try的聲明中,并且使用這個資源在try-catch塊里面,當運行到這個try-catch之外的時候,運行環境會自動關閉這些資源,例如:
~~~
try (MyResource mr = new MyResource()) {
System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
e.printStackTrace();
}
~~~
具體的用法參考這篇文章:[Java 7 Automatic Resource Management](http://www.journaldev.com/592/try-with-resource-example-java-7-feature-for-automatic-resource-management)
**創建自定義的異常類**
java中提供了很多我們可以使用的異常,但是有時我們對指定的帶有適當的信息以及我們需要跟蹤的自定義字段的異常,需要通過創建自定義的異常類去通知調用者。例如:我們寫了一個方法只去處理文本文件,因此當其他類型的文件輸入的時候,我們需要將對應的錯誤代碼提供給調用者。
下面有一個例子:?
MyException.java
~~~
package com.journaldev.exceptions;
public class MyException extends Exception {
private static final long serialVersionUID = 4664456874499611218L;
private String errorCode="Unknown_Exception";
public MyException(String message, String errorCode){
super(message);
this.errorCode=errorCode;
}
public String getErrorCode(){
return this.errorCode;
}
}
~~~
CustomExceptionExample.java
~~~
package com.journaldev.exceptions;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class CustomExceptionExample {
public static void main(String[] args) throws MyException {
try {
processFile("file.txt");
} catch (MyException e) {
processErrorCodes(e);
}
}
private static void processErrorCodes(MyException e) throws MyException {
switch(e.getErrorCode()){
case "BAD_FILE_TYPE":
System.out.println("Bad File Type, notify user");
throw e;
case "FILE_NOT_FOUND_EXCEPTION":
System.out.println("File Not Found, notify user");
throw e;
case "FILE_CLOSE_EXCEPTION":
System.out.println("File Close failed, just log it.");
break;
default:
System.out.println("Unknown exception occured, lets log it for further debugging."+e.getMessage());
e.printStackTrace();
}
}
private static void processFile(String file) throws MyException {
InputStream fis = null;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new MyException(e.getMessage(),"FILE_NOT_FOUND_EXCEPTION");
}finally{
try {
if(fis !=null)fis.close();
} catch (IOException e) {
throw new MyException(e.getMessage(),"FILE_CLOSE_EXCEPTION");
}
}
}
}
~~~
**異常處理最佳實踐**
1、使用具體的異常(Use Specific Exceptions)?
異常層次的基類不能提供任何有用的信息,這也是為什么java里面有如此多的異常類,例如:IOException有子類FileNotFoundException和FileNotFoundException等,我們應該throw和catch具體的異常,這樣調用者就知道產生異常的根本原因,以便及時處理。這也是調試變得更加容易同時幫助客戶應用適當的處理異常。
2、盡可能早的拋出異常(Throw Early or Fail-Fast)?
我們應該嘗試盡可能早的把異常拋出來,看看上面processFile()方法,如果我們傳遞一個null參數給這個方法,我們可能得到下面的異常:
~~~
Exception in thread "main" java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:134)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
~~~
我們在調試的過程中需要確定異常發生的位置,上面的提示并不是清楚的,如果我們實現這個邏輯去盡可能早的檢查這個異常,例如:
~~~
private static void processFile(String file) throws MyException {
if(file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");
//further processing
}
~~~
上面的錯誤信息提示如下,可以看到它可以更清楚的顯示異常發生的位置和信息。
~~~
com.journaldev.exceptions.MyException: File name can't be null
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
~~~
3、晚一些捕獲異常(Catch Late)?
由于java強制我們需要處理可檢查的異常或者在方法中使用throws標識,有時候,一些程序員會去捕獲異常并且打印錯誤信息,這種行為是不好的,因為這樣調用程序就不能得到異常通知,我們應該在我們可以處理它的時候進行異常的捕獲。例如,上面我們將異常拋給調用它的方法去進行處理。如果另一個程序希望使用不同的方法進行處理,它也可以得到這個異常進行處理,所以,我們應該把異常拋給它的調用者,讓他們來決定如何進行處理。
4、關閉資源(Closing Resources)?
異常會中斷程序的執行,所以應該在finally塊中關閉所有的資源或者使用java 7.0中的try-with-resources去讓運行環境幫你自動關閉。
5、記錄異常(Logging Exceptions )?
我們應該總是記錄異常信息,拋出異常提供更詳細的信息。這樣調用者就可以很容易知道為什么發生。我們應該總是避免僅僅為了消耗異常而不能提供有用信息的空catch塊。
6、一個try塊處理多個異常(Single catch block for multiple exceptions)?
許多時候,我們打印異常信息并且提供異常信息給用戶,這樣我們可以使用java 7中的特性,在一個try塊中處理多個異常。
7、使用自定義異常(Using Custom Exceptions)?
自定義異常處理機制比普通的異常捕獲會更加的完美,我們可以創建一個帶有異常碼的自定義異常并且調用程序可以處理這些錯誤碼。這樣我們可以創建一個工具方法來處理不同的錯誤碼并且使用它。
8、命名規范和打包(Naming Conventions and Packaging)?
如果我們創建一個自定義的異常,確保這個異常以Exception結尾,這樣就可以很清楚的知道它是一個異常,也可以像JDK一樣對它進行打包,例如:IOException是所有IO操作異常的基類。
9、恰當的使用異常(Use Exceptions Judiciously)?
使用異常是有代價的,并不是在所有的地方都需要使用異常,我們可以返回一個boolean變量來告訴調用者是否操作成功,這個是相當有用的,這樣操作就是可選的而且你的程序也不會因為失敗而中斷。
10、文檔化異常的拋出(Document the Exceptions Thrown)?
使用javadoc的@throws是明確的指定某個方法會拋出異常,這樣非常有用的,當你提供一個接口給其他的應用去使用。
原文鏈接:[Java Exception Handling Tutorial with Examples and Best Practices](http://www.javacodegeeks.com/2013/07/java-exception-handling-tutorial-with-examples-and-best-practices.html)
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)