本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html
![]()
以前咱們介紹的基本類型、類、接口、枚舉都是在表示和操做數據,操做的過程當中可能有不少出錯的狀況,出錯的緣由多是多方面的,有的是不可控的內部緣由,好比內存不夠了、磁盤滿了,有的是不可控的外部緣由,好比網絡鏈接有問題,更多的多是程序的編程錯誤,好比引用變量未初始化就直接調用實例方法。java
這些非正常狀況在Java中統一被認爲是異常,Java使用異常機制來統一處理,因爲內容較多,咱們分爲兩節來介紹,本節介紹異常的初步概念,以及異常類自己,下節主要介紹異常的處理。git
咱們先來經過一些例子認識一下異常。程序員
咱們來看段代碼:數據庫
public class ExceptionTest {
public static void main(String[] args) {
String s = null;
s.indexOf("a");
System.out.println("end");
}
}
複製代碼
變量s沒有初始化就調用其實例方法indexOf,運行,屏幕輸出爲:編程
Exception in thread "main" java.lang.NullPointerException
at ExceptionTest.main(ExceptionTest.java:5)
複製代碼
輸出是告訴咱們:在ExceptionTest類的main函數中,代碼第5行,出現了空指針異常(java.lang.NullPointerException)。數組
但,具體發生了什麼呢?當執行s.indexOf("a")的時候,Java系統發現s的值爲null,沒有辦法繼續執行了,這時就啓用異常處理機制,首先建立一個異常對象,這裏是類NullPointerException的對象,而後查找看誰能處理這個異常,在示例代碼中,沒有代碼能處理這個異常,Java就啓用默認處理機制,那就是打印異常棧信息到屏幕,並退出程序。bash
在介紹函數調用原理的時候,咱們介紹過棧,異常棧信息就包括了從異常發生點到最上層調用者的軌跡,還包括行號,能夠說,這個棧信息是分析異常最爲重要的信息。微信
Java的默認異常處理機制是退出程序,異常發生點後的代碼都不會執行,因此示例代碼中最後一行System.out.println("end")不會執行。網絡
咱們再來看一個例子,代碼以下:
public class ExceptionTest {
public static void main(String[] args) {
if(args.length<1){
System.out.println("請輸入數字");
return;
}
int num = Integer.parseInt(args[0]);
System.out.println(num);
}
}
複製代碼
args表示命令行參數,這段代碼要求參數爲一個數字,它經過Integer.parseInt將參數轉換爲一個整數,並輸出這個整數。參數是用戶輸入的,咱們沒有辦法強制用戶輸入什麼,若是用戶輸的是數字,好比123,屏幕會輸出123,但若是用戶輸的不是數字,好比abc,屏幕會輸出:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at ExceptionTest.main(ExceptionTest.java:7)
複製代碼
出現了異常NumberFormatException。這個異常是怎麼產生的呢?根據異常棧信息,咱們看相關代碼:
這是NumberFormatException類65行附近代碼:
64 static NumberFormatException forInputString(String s) {
65 return new NumberFormatException("For input string: \"" + s + "\"");
66 }
複製代碼
這是Integer類492行附近代碼:
490 digit = Character.digit(s.charAt(i++),radix);
491 if (digit < 0) {
492 throw NumberFormatException.forInputString(s);
493 }
494 if (result < multmin) {
495 throw NumberFormatException.forInputString(s);
496 }
複製代碼
將這兩處合爲一行,主要代碼就是:
throw new NumberFormatException(...)
複製代碼
new NumberFormatException(...)是咱們容易理解的,就是建立了一個類的對象,只是這個類是一個異常類。throw是什麼意思呢?就是拋出異常,它會觸發Java的異常處理機制。在以前的空指針異常中,咱們沒有看到throw的代碼,能夠認爲throw是由Java虛擬機本身實現的。
throw關鍵字能夠與return關鍵字進行對比,return表明正常退出,throw表明異常退出,return的返回位置是肯定的,就是上一級調用者,而throw後執行哪行代碼則常常是不肯定的,由異常處理機制動態肯定。
異常處理機制會從當前函數開始查找看誰"捕獲"了這個異常,當前函數沒有就查看上一層,直到主函數,若是主函數也沒有,就使用默認機制,即輸出異常棧信息並退出,這正是咱們在屏幕輸出中看到的。
對於屏幕輸出中的異常棧信息,程序員是能夠理解的,但普通用戶沒法理解,也不知道該怎麼辦,咱們須要給用戶一個更爲友好的信息,告訴用戶,他應該輸入的是數字,要作到這一點,咱們須要本身"捕獲"異常。
"捕獲"是指使用try/catch關鍵字,咱們看捕獲異常後的示例代碼:
public class ExceptionTest {
public static void main(String[] args) {
if(args.length<1){
System.out.println("請輸入數字");
return;
}
try{
int num = Integer.parseInt(args[0]);
System.out.println(num);
}catch(NumberFormatException e){
System.err.println("參數"+args[0]
+"不是有效的數字,請輸入數字");
}
}
}
複製代碼
咱們使用try/catch捕獲並處理了異常,try後面的大括號{}內包含可能拋出異常的代碼,括號後的catch語句包含能捕獲的異常和處理代碼,catch後面括號內是異常信息,包括異常類型和變量名,這裏是NumberFormatException e,經過它能夠獲取更多異常信息,大括號{}內是處理代碼,這裏輸出了一個更爲友好的提示信息。
捕獲異常後,程序就不會異常退出了,但try語句內異常點以後的其餘代碼就不會執行了,執行完catch內的語句後,程序會繼續執行catch大括號外的代碼。
這樣,咱們就對異常有了一個初步的瞭解,異常是相對於return的一種退出機制,能夠由系統觸發,也能夠由程序經過throw語句觸發,異常能夠經過try/catch語句進行捕獲並處理,若是沒有捕獲,則會致使程序退出並輸出異常棧信息。異常有不一樣的類型,接下來,咱們來認識一下。
NullPointerException和NumberFormatException都是異常類,全部異常類都有一個共同的父類Throwable,它有4個public構造方法:
有兩個主要參數,一個是message,表示異常消息,另外一個是cause,表示觸發該異常的其餘異常。異常能夠造成一個異常鏈,上層的異常由底層異常觸發,cause表示底層異常。
Throwable還有一個public方法用於設置cause:
Throwable initCause(Throwable cause) 複製代碼
Throwable的某些子類沒有帶cause參數的構造方法,就能夠經過這個方法來設置,這個方法最多隻能被調用一次。
全部構造方法中都有一句重要的函數調用:
fillInStackTrace();
複製代碼
它會將異常棧信息保存下來,這是咱們能看到異常棧的關鍵。
Throwable有一些經常使用方法用於獲取異常信息:
void printStackTrace() 複製代碼
打印異常棧信息到標準錯誤輸出流,它還有兩個重載的方法:
void printStackTrace(PrintStream s) void printStackTrace(PrintWriter s) 複製代碼
打印棧信息到指定的流,關於PrintStream和PrintWriter咱們後續文章介紹。
String getMessage() Throwable getCause() 複製代碼
獲取設置的異常message和cause
StackTraceElement[] getStackTrace()
複製代碼
獲取異常棧每一層的信息,每一個StackTraceElement包括文件名、類名、函數名、行號等信息。
以Throwable爲根,Java API中定義了很是多的異常類,表示各類類型的異常,部分類示意以下:
Throwable是全部異常的基類,它有兩個子類Error和Exception。
Error表示系統錯誤或資源耗盡,由Java系統本身使用,應用程序不該拋出和處理,好比圖中列出的虛擬機錯誤(VirtualMacheError)及其子類內存溢出錯誤(OutOfMemoryError)和棧溢出錯誤(StackOverflowError)。
Exception表示應用程序錯誤,它有不少子類,應用程序也能夠經過繼承Exception或其子類建立自定義異常,圖中列出了三個直接子類:IOException(輸入輸出I/O異常),SQLException(數據庫SQL異常),RuntimeException(運行時異常)。
RuntimeException(運行時異常)比較特殊,它的名字有點誤導,由於其餘異常也是運行時產生的,它表示的實際含義是unchecked exception (未受檢異常),相對而言,Exception的其餘子類和Exception自身則是checked exception (受檢異常),Error及其子類也是unchecked exception。
checked仍是unchecked,區別在於Java如何處理這兩種異常,對於checked異常,Java會強制要求程序員進行處理,不然會有編譯錯誤,而對於unchecked異常則沒有這個要求。下節咱們會進一步解釋。
RuntimeException也有不少子類,下表列出了其中常見的一些:
異常 | 說明 |
---|---|
NullPointerException | 空指針異常 |
IllegalStateException | 非法狀態 |
ClassCastException | 非法強制類型轉換 |
IllegalArgumentException | 參數錯誤 |
NumberFormatException | 數字格式錯誤 |
IndexOutOfBoundsException | 索引越界 |
ArrayIndexOutOfBoundsException | 數組索引越界 |
StringIndexOutOfBoundsException | 字符串索引越界 |
這麼多不一樣的異常類其實並無比Throwable這個基類多多少屬性和方法,大部分類在繼承父類後只是定義了幾個構造方法,這些構造方法也只是調用了父類的構造方法,並無額外的操做。
那爲何定義這麼多不一樣的類呢?主要是爲了名字不一樣,異常類的名字自己就表明了異常的關鍵信息,不管是拋出仍是捕獲異常時,使用合適的名字都有助於代碼的可讀性和可維護性。
除了Java API中定義的異常類,咱們也能夠本身定義異常類,通常經過繼承Exception或者它的某個子類,若是父類是RuntimeException或它的某個子類,則自定義異常也是unchecked exception,若是是Exception或Exception的其餘子類,則自定義異常是checked exception。
咱們經過繼承Exception來定義一個異常,代碼以下:
public class AppException extends Exception {
public AppException() {
super();
}
public AppException(String message, Throwable cause) {
super(message, cause);
}
public AppException(String message) {
super(message);
}
public AppException(Throwable cause) {
super(cause);
}
}
複製代碼
和不少其餘異常類同樣,咱們沒有定義額外的屬性和代碼,只是繼承了Exception,定義了構造方法並調用了父類的構造方法。
本節,咱們經過兩個例子對異常作了基本介紹,介紹了try/catch和throw關鍵字及其含義,同時介紹了Throwable以及以它爲根的異常類體系。
下一節,讓咱們進一步探討異常。
未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。用心原創,保留全部版權。