目錄:
一、 異常繼承體系
二、 發生異常到時候,程序的執行特征:
三、 異常與錯誤的區別
四、 拋出異常 throw
五、 聲明異常 throws
六、 捕獲異常 try…catch…finally
七、 try…catch…finally 異常處理的組合方式
八、 異常在方法重寫中細節
九、 Throwable 類中的常用方法
十、 try和finally中都有return語句,執行哪一個 return?
十一、 自定義異常
一、 異常繼承體系
- 異常是程序運行過程中出現的錯誤。Java 把異常當作對象來處理,把異常信息封裝成了一個類,并定義一個基類 java.lang.Throwable 作為所有異常的超類。
-
Throwable
: 它是所有錯誤與異常的超類(祖宗類)
,有兩個子類Error 和Exception-
Error
: 錯誤。程序無法處理的錯誤,比如OutOfMemoryError、ThreadDeath等。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。 -
Exception
: 異常。程序本身可以處理的異常,程序中應當盡可能去處理這些異常。-
RuntimeException
: 運行期異常, JAVA程序運行過程中出現的問題 -
CheckableException
: 編譯時異常或者可檢查異常, 是除了RuntimeExecption(及其子類)之外的所有 Exception
-
-
- 運行時期異常:
- 方法中拋出運行時期異常,方法定義中無需throws聲明,調用者也無需處理此異常
- 運行時期異常一旦發生,需要程序人員修改源代碼.
- 編譯時異常: 必須在編譯前處理,否則無法通過編譯
二、 發生異常到時候,程序的執行特征:
- 我們的代碼,從發生異常的地方,開始被一分為二
- 在異常發生之前的代碼,都可以正常運行,之后的代碼不會執行
- 當異常發生的時候,這個異常被 jvm 所捕獲,并將這個異常的所有相關信息,創建為一個異常對象,然后將該異常對象的信息輸出到控制臺(執行的是虛擬機默認的異常處理代碼)
- 終止當前程序
三、 異常與錯誤的區別
-
異常
:指程序在編譯、運行期間發生了某種異常(XxxException),我們可以對異常進行具體的處理。若不處理異常,程序將會結束運行。
-
錯誤
:指程序在運行期間發生了某種錯誤(XxxError),Error 錯誤通常沒有具體的處理方式,程序將會結束運行。Error錯誤的發生往往都是系統級別的問題,都是jvm所在系統發生并反饋給jvm的。我們無法針對處理,只能修正代碼。
四、 拋出異常 throw
- 在 java 中,提供了一個
throw
關鍵字,它用來拋出一個指定的(Throwable類型)異常對象. - 一般會用于程序出現某種邏輯時,程序員主動拋出某種特定類型的異常
- 具體步驟:
- 創建一個異常對象, 封裝一些提示信息(信息可以自己編寫).
- 通過關鍵字 throw, 將這個異常對象告知給調用者.
- throw 用在方法內,用來拋出一個異常對象,將這個異常對象傳遞到調用者處,并結束當前方法的執行。
-
使用格式:
throw new 異常類名(參數);
throw new NullPointerException("要訪問的arr數組不存在");
throw new ArrayIndexOutOfBoundsException("該索引在數組中不存在,已超出范圍");
//具體范例:
public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new NumberFormatException();
} else {
System.out.println(s);
}
//function();
}
-
注意事項:
- 如果拋出的異常對象屬于可檢查的異常,必須在該方法頭部,聲明拋出此異常, 即:
throws 要拋出的異常類型
- 其次,對于拋出可檢查的異常,還必須與方法的異常列表中的異常兼容
-
如果父類方法聲明了異常列表:
- 子類可以不聲明異常列表
- 子類方法有自己異常列表時,必須保證,子類的異常列表所包含的異常類型,與父類中所包含的異常類型兼容
- 如果拋出的異常對象屬于可檢查的異常,必須在該方法頭部,聲明拋出此異常, 即:
五、 聲明異常 throws
- 聲明:將問題標識出來,報告給調用者
-
throws
是方法可能拋出異常的聲明, 如果定義功能時有問題發生需要報告給調用者。可以通過在方法上使用 throws 關鍵字進行聲明。
- 對于聲明了會拋出可檢查異常的方法, 就意味著這個方法會產生可檢查異常,所以,一旦調用該方法就必須對該方法做異常處理
- throws 用于進行異常類的聲明,若該方法可能有多種異常情況產生,那么在 throws 后面可以寫多個異常類,用逗號隔開。
-
聲明異常格式:
修飾符 返回值類型 方法名(參數) throws <異常列表> { }
public static void function() throws NumberFormatException{
String s = "abc";
System.out.println(Double.parseDouble(s));
}
public static void main(String[] args) {
try {
function();
} catch (NumberFormatException e) {
System.err.println("非數據類型不能轉換。");
}
}
六、 捕獲異常 try…catch…finally
1. 概述
- 捕獲:Java中對異常有針對性的語句進行捕獲,可以對出現的異常進行指定方式的處理
- 捕獲異常格式:
try {
//需要被檢測的語句。
}
catch(異常類 e) { //try中拋出的是什么異常,在括號中就定義什么異常類型。
//異常的處理語句。
}
finally {
//一定會被執行的語句。
}
//try:該代碼塊中編寫可能產生異常的代碼。
//catch:用來進行某種異常的捕獲,實現對捕獲到的異常進行處理。
//finally:有一些特定的代碼無論異常是否發生,都需要執行。另外,因為異常會引發程序跳轉,導致有些語句執行不到。而finally就是解決這個問題的,在finally代碼塊中存放的代碼都是一定會被執行的。
- 某個函數或某段程序塊不管會不會,有沒可能拋出異常,都可以加
try{...}catch{...}
去捕捉它。
2. 異常處理流程:
- 首先,當異常在 try 代碼塊中發生的時候,虛擬機首先捕獲這個異常,創建一個異常對象(包含本次異常的所有詳細信息)
- 虛擬機會把這個異常,拋出給catch代碼塊(類似于方法調用,虛擬機會調用catch代碼塊中,處理異常的代碼)
- 執行catch代碼塊,中的處理異常的代碼
- 沒有終止我們應用程序,而是從catch語句之后的代碼開始,繼續執行我們的應用程序
在try語句塊中,一旦發生異常,我們的代碼仍然被一分為二
- 在異常發生之前帶代碼都正常運行
- 在異常發生之后的代碼,不會執行,而是直接跳轉到我們自己處理異常的catch代碼塊中
七、 try…catch…finally 異常處理的組合方式
- try catch finally組合: 檢測異常,并傳遞給 catch 處理,并在 finally 中進行資源釋放。
即使在finally代碼塊執行了return語句,finally代碼塊中的代碼,仍然會執行
特殊情況: 走到finally之前 JVM 退出了,就不會執行finally了
finally在開發中的應用:用于釋放資源
- try catch組合 : 對代碼進行異常檢測,并對檢測的異常傳遞給catch處理。對異常進行捕獲處理
- 多個try catch組合 當可能有多種類型的異常發生的時候,我把可能產生某異常類型的代碼分開,分別放在不同的try-catch代碼塊中
- 執行特征是:
- 每個try-catch中是否拋出異常,相互獨立的
- 在實際執行的時候,每一個try-catch代碼塊可能都產生異常。
- 實際開發的時候,一般來講:
- 效率角度,try 塊中方的代碼越少越好
- 開發的時候,會將相關的異常,放在同一個 try 塊
- 相關異常: 一個連續的流程中,可能發生的一系列異常
- 所以一系列相關的流程中,一旦前一步出現了異常,就會導致,即使后面的流程正常執行,其實也已經沒有意義了
- 一個try 多個catch組合 : 對代碼進行異常檢測,并對檢測的異常傳遞給catch處理。對每種異常信息進行不同的捕獲處理。
這種異常處理方式,要求多個catch中的異常不能相同,
若catch中的多個異常之間有子父類異常的關系,那么先寫子類異常類型,再寫父類異常類型
當發生異常的時候, 最多執行一個catch分支的代碼
public class Demo {
public static void main(String[] args) {
try{
int i = 10;
//可能發生異常的語句
int j = i / 0;
System.out.println("try after exception");
//空指針異常
int[] a = null;
System.out.println(a[0]);
//數組越界異常
int[] b = {1, 2, 3};
System.out.println(b[3]);
}catch (ArithmeticException e) {
System.out.println("發生了除0異常");
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) { //一個catch分支處理多種類型的異常
System.out.println("發生了數組異常");
}
}
}
- try finally 組合: 對代碼進行異常檢測,檢測到異常后因為沒有catch,所以一樣會被默認 jvm 拋出。異常是沒有捕獲處理的。但是功能所開啟資源需要進行關閉,所以 finally 只為關閉資源
八、 異常在方法重寫中細節
- 子類覆蓋父類方法時,如果父類的方法聲明異常,子類只能聲明父類異常或者該異常的子類,或者不聲明。
class Fu {
public void method () throws RuntimeException {
}
}
class Zi extends Fu {
public void method() throws RuntimeException { } //拋出父類一樣的異常
//public void method() throws NullPointerException{ } //拋出父類子異常
}
- 當父類方法聲明多個異常時,子類覆蓋時只能聲明多個異常的子集。
class Fu {
public void method () throws NullPointerException, ClassCastException{
}
}
class Zi extends Fu {
public void method()throws NullPointerException, ClassCastException { }
public void method() throws NullPointerException{ } //拋出父類異常中的一部分
public void method() throws ClassCastException { } //拋出父類異常中的一部分
}
- 當被覆蓋的方法沒有異常聲明時,子類覆蓋時無法聲明異常的。
九、 Throwable類中的常用方法
-
getCause()
:返回拋出異常的原因,即異常提示信息。如果 cause 不存在或未知,則返回 null。 -
getMessage()
:返回異常的消息信息,即該異常的名稱與詳細信息字符串 -
printStackTrace()
:在控制臺輸出該異常的名稱與詳細信息字符串、異常出現的代碼位置
十、 try和finally中都有return語句,執行哪一個return?
- 首先要確定的一點是,不管有木有出現異常,finally塊中代碼都會執行
- 當try和catch中有return時,finally仍然會執行;
- finally是在return后面的表達式運算后執行的(此時并沒有返回運算后的值,而是先把要返回的值保存起來,不管finally中的代碼怎么樣,返回的值都不會改變,任然是之前保存的值),所以函數返回值是在finally執行前確定的;
- finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。
問:如果try和finally語句里面都有return,會執行哪一個呢?
首先,在程序沒有異常的情況下,首先執行到try里面的語句,但是只執行到了return里面的****expression,expression首先存放在操作數棧頂,然后復制到局部變量區,并沒有執行返回語句return(執行返回語句通常意味著程序執行結束)。然后執行finally,當執行到finally里面的return時候,會將return語句執行完整,此時程序已經有了返回值,因為,執行結束。
- 總結:執行try塊,執行到return語句時,先執行return的語句,但是不返回到main 方法,接下來執行finally塊,遇到finally塊中的return語句,執行,并將值返回到main方法,這里就不會再回去返回try塊中計算得到的值
十一、 自定義異常
1. 概述
- 如果Java沒有提供你需要的異常,則可以自定義異常類。
- 編譯時異常繼承 Exception,運行時異常繼承 RuntimeException
- 格式:
Class 異常名 extends Exception{ //或繼承RuntimeException
public 異常名(){
}
public 異常名(String s){
super(s);
}
}
2. 示例
需求描述:
定義Person類,包含name與age兩個成員變量。
在Person類的有參數構造方法中,進行年齡范圍的判斷,若年齡為負數或大于200歲,則拋出NoAgeException異常,異常提示信息“年齡數值非法”。
要求:在測試類中,調用有參數構造方法,完成Person對象創建,并進行異常的處理。
//自定義異常類
class NoAgeException extends Exception{
NoAgeException() {
super();
}
NoAgeException(String message) {
super(message);
}
}
//Person類
class Person{
private String name;
private int age;
Person(String name,int age) throws NoAgeException {
//加入邏輯判斷
if(age<0 || age>200) {
throw new NoAgeException(age+",年齡數值非法");
}
this.name = name;
this.age = age;
}
//定義Person對象對應的字符串表現形式。覆蓋Object中的toString方法。
public String toString() {
return "Person[name="+name+",age="+age+"]";
}
}
//測試類
class ExceptionDemo{
public static void main(String[] args) {
try {
Person p = new Person("xiaoming",20);
System.out.println(p);
}
catch (NoAgeException ex){
System.out.println("年齡異常啦");
}
System.out.println("over");
}
}
總結一下,構造函數到底拋出這個NoAgeException是繼承Exception呢?還是繼承RuntimeException呢?
繼承Exception,必須要throws聲明,一聲明就告知調用者進行捕獲,一旦問題處理了調用者的程序會繼續執行。
繼承RuntimeExcpetion,不需要throws聲明的,這時調用是不需要編寫捕獲代碼的,因為調用根本就不知道有問題。一旦發生NoAgeException,調用者程序會停掉,并有jvm將信息顯示到屏幕,讓調用者看到問題,修正代碼。