對於異常情況,例如,可能造成程序崩潰的錯誤輸入,Java是通過捕獲機制來處理異常錯誤。當程序出錯時,我們不可能總是及時和用戶溝通,所以希望記錄出現的問題,以備日後進行分析。
如何處理錯誤
當一個用戶在運行程序期間,由於程序的錯誤或一些外部環境的影響造成用戶數據的丟失,用戶就有可能不再使用這個程序了。爲了避免這類事情發生,我們應該注意以下幾點。
- 向用戶通知錯誤的原因
- 保存所有的工作結果(日誌)
- 允許用戶以妥善的形式退出程序
異常分類
所有Java異常對象都是由Throwable繼承而來。如果Java內置的異常類不滿足我們的需求,我們可以創建自己的異常類。下面是我畫的Java異常結構的一個簡化示意圖。
Error | 是描述Java運行時系統內部錯誤和資源耗盡錯誤。應用程序不應該拋出這種類型的對象。如果出現了這樣內部錯誤,除了告知客戶,並盡力使用程序安全地終止之外,別無他法,不過這樣的情況很少出現。 |
Exception | 是描述Java應用程序拋出和處理的非嚴重型錯誤,程序錯誤導致的異常就屬於RuntimeException;而程序本身沒有問題,但由於I/O錯誤這類問題導致的異常屬於其他異常。 |
派生於RuntimeException的異常包含下面幾種情況:
- 錯誤的類型轉換
- 數組下標越界
- 空指針異常
不是派生於RuntimeException的異常(其他異常):
- 在文件尾部後面讀取數據
- 打開一個不存在的文件
- 根據給定的字符串查找Class對象,而這個字符串表示的類不存在
如果存在RuntimeException異常,那麼就一定是你的問題這是一條非常有道理的規則,不知道大家認不認可,我們應該在變量使用前檢測是否爲null可以防止出現NullPointerException(空指針異常);通過檢測數組下標是否越界可以避免ArrayIndexOutOfBoundsException異常。
拋出異常
當我們需要在一個方法中拋出一個異常時,我們使用throw後加某異常類的實例,程序會在此向客戶端程序(調用這段代碼的程序)拋出對應異常並在此退出(相當於return)。另外需要注意的是,我們必須在定義該方法的時候指明異常類型,比如下面這段代碼會拋出testException異常。
public void demo() throws testException,AException,BException{
throw new testException();
}
不同的異常類之間用逗號隔開即可,在這種情況下我們不必須throw每個異常類的實例(),但是客戶端代碼必須要catch到每個異常類:
public class MyException {
public static void main(String[] args){
MyException e = new MyException();
try {
e.demo();
} catch (testException e1) {
e1.printStackTrace();
} catch (BException e1) {
e1.printStackTrace();
} catch (AException e1) {
e1.printStackTrace();
}
}
public void a() throws testException,AException,BException{
throw new testException();
}
}
class testException extends Exception {};
class AException extends Exception{};
class BException extends Exception{};
捕獲異常
1.捕獲單個異常
try {
//code more code
}catch (Exception e){
//handler for this type
}
2.捕獲多個異常
注:捕獲多個異常不僅讓你代碼看起來更簡單,還會更加高效。
try {
...
}catch (FileNotFoundException e){
...
}catch (UnknownHostException e){
...
}catch (IOException e){
...
}
異常對象可能包含與異常本身有關的信息。要想獲取對象更多的信息,可以嘗試使用e.getMessage(),得到詳細的錯誤信息(如果有的話),或者使用e.getClass().getName()得到異常對象的實際類型。
3.再次拋出異常與異常鏈
在catch子句中可以拋出一個異常,這樣做是爲了改變異常的類型。如果我們開發了一個供其他程序使用的子系統,那麼表示子系統故障的異常類型可能會產生很多種解釋。ServletException就是如此,執行servlet代碼可能不想知道發生錯誤的細節,但希望明確知道servlet是否有問題。
try {
//連接database
}catch (SQLException e){
throw new ServeletException("database error:"+e.getMessage());//throw拋異常
}
這裏,ServeletException用帶有異常信息文本的構造器來構造。不過可以有一種更好的處理方法,並且將原始異常設置爲新異常的原因:
try {
//連接database
}catch (SQLException e){
Throwable se = new ServeletException("database error");
se.initCause(e);
throw se;
}
當捕獲到異常時,就可以用下面這條語句重新得到原始異常:
Throwable e = se.getCause();
強烈建議使用這種包裝技術。可以讓用戶拋出子系統的高級異常,而不會丟失原始異常的細節。
小提示:如果在一個方法中發生異常,而又不允許拋出它,那麼包裝技術就十分重要。我們可以捕獲這個異常,並將它包裝成一個運行時異常。比如:有時候你可能只想記錄一個異常,再將它重新拋出,而不做任何的改變,代碼示例如下:
try {
//連接database
}catch (Exception e){
logger.log(level,message,e);//日誌
throw e;
}
4.finally子句
不管是否有異常被捕獲,finally子句中的代碼總是會被執行。當finally包含return時,將會出現意想不到的結果,假設從try中退出return。在方法返回前,finally中的內容將會被執行。如果finally中也有return,這個返回值會覆蓋原始的返回值,例子如下:
public static int(int n){
try{
int r = n*n;
return r;
}
finally{
if(n==2) return 0;
}
}
如果在finally中關閉資源,最好加上處理,防止異常,這是最傳統的做法。下面第五點將給大家介紹jdk7之後資源關閉的方式。
finally{
try{
in.close();
}catch(Exception e){
throw e;
}
}
5.try-with-resource(帶資源的try)
如果打開了外部資源(文件、數據庫連接、網絡連接等),我們必須在這些外部資源使用完畢後,手動關閉它們。因爲外部資源不由JVM管理,無法享用JVM的垃圾回收機制,如果我們不在編程時確保在正確的時機關閉外部資源,就會導致外部資源泄露,緊接着就會出現文件被異常佔用,數據庫連接過多導致連接池溢出等諸多很嚴重的問題。
當try退出時,會自動調用close方法,去讀取單詞中的所有單詞,示例如下:
try(Scanner in = new Scanner(New FileInputStream("/user/share/dict/words")),"UTF-8"){
while(in.hasNext()){
System.out.println(in.next());
}
}
如果try拋出一個異常,而且close方法也拋出一個異常,這樣就會帶來一個難題。到資源的try可以很好地處理。原來的close方法拋出的異常被抑制。這些異常將會自動捕獲,並由addSuperssed方法增加到原來的異常。如果對這些異常感興趣,可以調用getSupperssed方法,它會得到從close拋出並抑制的異常列表。
自定義異常
有時候Java內置異常不滿足於我們的需求,我們就需要用到自定義異常。比如:系統中有些錯誤是符合Java語法的,但不符合我們項目的業務邏輯;企業項目是分模塊或者分功能開發的 ,使用自定義異常類就統一了對外異常展示的方式等。
所有異常都必須是 Throwable 的子類。 |
如果希望寫一個檢查性異常類,則需要繼承 Exception 類。 |
如果你想寫一個運行時異常類,那麼需要繼承 RuntimeException 類。 |
創建一個檢查性異常類
public class DatamartAssertException extends Exception {
private static final long serialVersionUID = 7358625131054183395L;
public DatamartAssertException() {
}
public DatamartAssertException(String message) {
super(message);
}
}
拋出和使用自定義異常
public static void main(String[] args) throws DatamartAssertException{
CloseableHttpClient httpClient = null;
try{
//創建httpClient實例
httpClient = HttpClients.createDefault();
//地址url
String url = "www.baidu.coms";
//創建HttpPost實例
HttpPost method = new HttpPost(url);
//設置請求讀取超時時間 60秒
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(60000).setConnectionRequestTimeout(60000)
.setSocketTimeout(60000).build();
method.setConfig(requestConfig);
//執行請求
try{
HttpResponse result = httpClient.execute(method);
/**請求發送成功,並得到響應**/
if (result.getStatusLine().getStatusCode() == 200) {
String str = "";
/**讀取服務器返回過來的json字符串數據**/
str = EntityUtils.toString(result.getEntity());
}
}catch (Exception e){
e.printStackTrace();
}
}catch(ConnectTimeoutException e){
// 捕獲超時異常 並反饋給調用者
throw new DatamartAssertException("請求超時!");
}catch(ConnectException e) {
throw new DatamartAssertException("請求超時!");
}catch (Exception e){
e.printStackTrace();
}
finally{
/**關閉連接,釋放資源**/
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
總結:不要過分的細化異常,不要壓制異常;早拋出,晚捕獲。
--------------如果大家喜歡我的博客,可以點擊左上角的關注哦。