Java中的異常(Exception)又稱為例外,是一個在程序執(zhí)行期間發(fā)生的事件,它中斷正在執(zhí)行程序的正常指令流。為了能夠及時有效地處理程序中的運行錯誤讥蔽,必須使用異常類
一、異常產(chǎn)生的原因和分類
異常產(chǎn)生的原因
在Java中異常產(chǎn)生画机,主要是有三種原因:
- 編寫程序代碼中的錯誤產(chǎn)生的異常冶伞,比如數(shù)組越界、空指針異常等步氏,這種異常叫做未檢查的異常碰缔,一般需要在類中處理這些異常
- Java內(nèi)部錯誤發(fā)生的異常,Java虛擬機產(chǎn)生異常
- 通過throw(拋出異常)語句手動生成的異常戳护,這種異常叫做檢查的異常金抡,一般是用來給方法調(diào)用者一些必要的信息
- Throwable:是異常體系的頂層類,其派生出兩個重要的子類, Error 和 Exception
而 Error 和 Exception 兩子類分別表示錯誤和異常 區(qū)別就是不檢查異常(Unchecked Exception)和檢查異常(Checked Exception)
- Exception 類用于用戶程序可能出現(xiàn)的異常情況腌且,它也是用來創(chuàng)建自定義異常類型類的類梗肝。
- Error 定義了在通常環(huán)境下不希望被程序捕獲的異常。Error 類型的異常用于 Java 運行時由系統(tǒng)顯示與運行時系統(tǒng)本身有關(guān)的錯誤铺董。堆棧溢出是這種錯誤的一例
異澄谆鳎可能在編譯時發(fā)生,也有可能在程序運行時發(fā)生精续,根據(jù)發(fā)生時機不同坝锰,可以分為:
運行時異常: RuntimeException 類及其子類異常,如 NullPointerException重付、IndexOutOfBoundsException 等顷级,這些異常是不檢查異常,程序中可以選擇捕獲處理确垫,也可以不處理弓颈。這些異常一般由程序邏輯錯誤引起帽芽,程序應(yīng)該從邏輯角度盡可能避免這類異常的發(fā)生
編譯時異常: RuntimeException 以外的異常,類型上都屬于 Exception 類及其子類翔冀。從程序語法角度講是必須進行處理的異常导街,如果不處理,程序就不能編譯通過纤子。如 IOException搬瑰、ClassNotFoundException 等以及用戶自定義的 Exception 異常,一般情況下不自定義檢查異常
二控硼、異常的處理
2.1 防御式編程
通知有兩種方式:
- LBYL 在操作之前就做充分的檢查
private static int divide() {
int a = 0, b = 0;
Scanner scanner = new Scanner(System.in);
a = scanner.nextInt();
b = scanner.nextInt();
if (b == 0) {
System.out.println("除數(shù)為0");
return 0;
} else {
return a / b;
}
}
缺點:正常流程和錯誤處理流程代碼混在一起, 代碼整體條理不清晰
- EAFP 先操作遇到問題再處理
private static int divide() {
int a = 0, b = 0;
try (Scanner scanner = new Scanner(System.in)) {
a = scanner.nextInt();
b = scanner.nextInt();
return a / b;
} catch (ArithmeticException exception) {
System.out.println("除數(shù)為0");
return 0;
}
}
優(yōu)點:正常流程和錯誤流程是分離開的, 程序員更關(guān)注正常流程泽论,代碼更清晰,容易理解代碼
處理異常的核心思想就是EAFP
2.2 異常的拋出(throw)
在編寫程序時象颖,如果程序中出現(xiàn)錯誤佩厚,這就需要將錯誤的信息通知給調(diào)用者
這里就可以借助關(guān)鍵字throw姆钉,拋出一個指定的異常對象说订,將錯誤信息告知給調(diào)用者
比如寫一個運行時異常
public static void func2(int a) {
if(a == 0) {
//拋出的是一個指定的異常,最多的使用方式是潮瓶,拋出一個自定義的異常
throw new RuntimeException("a==0");
}
}
public static void main(String[] args) {
func2(0);
}
注意:
- throw必須寫在方法體內(nèi)部
- 如果拋出的是編譯時異常陶冷,用戶就必須要處理,否則無法通過編譯
- 如果拋出的運行時異常毯辅,則可以不用處理埂伦,直接交給JVM處理
- JVM捕獲異常后,后面代碼不會執(zhí)行
2.3 異常的捕獲
2.3.1 throws異常聲明
throws處在方法聲明時參數(shù)列表之后思恐,當(dāng)方法中拋出編譯時異常沾谜,
用戶不想處理該異常,此時就可以借助throws將異常拋 給方法的調(diào)用者來處理胀莹。
格式:
修飾符 返回值類型 方法名(參數(shù)列表) throws 異常類型 {
}
public static void func2(int a) throws CloneNotSupportedException {
if(a == 0) {
throw new CloneNotSupportedException("a==0");
}
}
如果說方法內(nèi)部拋出了多個異常基跑,throws之后就必須跟多個異常類型,用逗號進行分隔
public static void func2(int a) throws CloneNotSupportedException, FileNotFoundException {
if(a == 0) {
throw new CloneNotSupportedException("a==0");
}
if(a == 1) {
throw new FileNotFoundException();
}
}
如果拋出多個異常類型有父子關(guān)系描焰,直接聲明父類
public static void func2(int a) throws Exception {
if(a == 0) {
throw new CloneNotSupportedException("a==0");
}
if(a == 1) {
throw new FileNotFoundException();
}
}
調(diào)用聲明拋出異常的方法時媳否,調(diào)用者必須對該異常進行處理,或者繼續(xù)使用throws拋出
public static void main(String[] args) throws FileNotFoundException, CloneNotSupportedException {
func2(0);
}
2.3.2 try-catch捕獲異常并處理
如果程序拋出異常荆秦,不處理異常篱竭,那就會交給JVM處理,JVM處理就會把程序立即終止
并且步绸,即使用了 try-catch 也必須捕獲一個對應(yīng)的異常掺逼,如果不是對應(yīng)異常,也會讓JVM進行處理
public static void main(String[] args) {
try {
int[] array = null;
System.out.println(array.length);
}catch (NullPointerException e) {
System.out.println("捕獲到了一個空指針異常瓤介!");
}
System.out.println("其他程序坪圾!");
}
如果try拋出多個異常晓折,就必須用多個catch進行捕獲
這里注意,用多個catch進行捕獲兽泄,不是同時進行捕獲的漓概,因為不可能同時拋不同的異常:
public static void main(String[] args) {
try {
int[] array = null;
System.out.println(array.length);
}catch (NullPointerException e) {
System.out.println("捕獲到了一個空指針異常!");
}catch (ArithmeticException e) {
System.out.println("捕獲到了一個算術(shù)異常病梢!");
}
System.out.println("其它代碼邏輯胃珍!");
}
如果異常之間具有父子關(guān)系,那就必須子類異常在前蜓陌,父類異常在后catch觅彰,不然會報錯:
public static void main(String[] args) {
try {
int[] array = null;
System.out.println(array.length);
}catch (NullPointerException e) {
System.out.println("捕獲到了一個空指針異常!");
}catch (Exception) {
System.out.println("捕獲到了一個算術(shù)異常钮热!");
}
System.out.println("其它代碼邏輯填抬!");
}
2.3.3 finally
finally用來進行資源回收,不論程序正常運行還是退出隧期,都需要回收資源
并且異常會引發(fā)程序的跳轉(zhuǎn)飒责,可能會導(dǎo)致有些語句執(zhí)行不到
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
int[] array = null;
System.out.println(array.length);
}catch (NullPointerException e) {
System.out.println("捕獲到了一個空指針異常!");
}catch (ArithmeticException e) {
System.out.println("捕獲到了一個算術(shù)異常仆潮!");
}finally {
scanner.close();
System.out.println("進行資源關(guān)閉宏蛉!");
}
System.out.println("其它代碼邏輯!");
}
如果不為空性置,那么finally還會被執(zhí)行嗎?
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
int[] array = {1,2,3};
System.out.println(array.length);
}catch (NullPointerException e) {
System.out.println("捕獲到了一個空指針異常拾并!");
}catch (ArithmeticException e) {
System.out.println("捕獲到了一個算術(shù)異常!");
}finally {
scanner.close();
System.out.println("進行資源關(guān)閉鹏浅!");
}
System.out.println("其它代碼邏輯嗅义!");
}
不管程序會不會拋出異常,finally都會執(zhí)行
三隐砸、自定義異常類
雖然java中有很多異常類之碗,但是在實際開發(fā)中所遇到的一些異常,不能完全表示 所以這就需要我們自定義異常類
3.1 自定義一個運行時異常
//自定義了一個運行時異常
public class MyException extends RuntimeException{
public MyException() {}
public MyException(String message) {
super(message);
}
}
3.2 寫一個類來捕獲這個自定義異常
public class Test04 {
public static void func (int a) {
throw new MyException("呵呵凰萨!");
}
public static void main(String[] args) {
try {
func(20);
}catch (MyException myException) {
myException.printStackTrace();
}finally {
System.out.println("sadasdasd");
}
}
}
3.3 下面寫一個用戶登錄的自定義異常類
class UserNameException extends RuntimeException {
public UserNameException() {}
public UserNameException(String message) {
super(message);
}
}
class PasswordException extends RuntimeException {
public PasswordException() {}
public PasswordException(String message) {
super(message);
}
}
public class LogIn {
private static String uName = "admin";
private static String pword = "1111";
public static void loginInfo(String userName, String password) {
if (!uName.equals(userName)) {
throw new UserNameException("用戶名錯誤继控!");
}
if (!pword.equals(password)) {
throw new RuntimeException("密碼錯誤!");
}
System.out.println("登錄成功胖眷!");
}
public static void main(String[] args) {
try {
loginInfo("admin","1111");
} catch (UserNameException e) {
e.printStackTrace();
} catch (PasswordException e) {
e.printStackTrace();
}
}
}
注意:
自定義異常默認會繼承 Exception 或者 RuntimeException:
- 繼承于 Exception 的異常默認是受查異常
- 繼承于 RuntimeException 的異常默認是非受查異常
四武通、try catch finally 執(zhí)行順序詳解
4.1 finally 語句不會執(zhí)行的四種情況
- 如果在 try 或 catch 語句中執(zhí)行了System.exit(0)
- 在執(zhí)行 finally 之前 jvm 崩潰了
- try 語句中執(zhí)行死循環(huán)
- 電源斷電
除以上四種情況外,finally 語句都會執(zhí)行
4.2 finally語句執(zhí)行原則
-
不管有沒有出現(xiàn)異常珊搀,finally 語句塊中代碼都會執(zhí)行
public void demo1(){ try { System.out.println(result); } catch (Exception e) { System.out.println(e.getMessage()); }finally { System.out.println("finally trumps. "); } } //輸出結(jié)果為: result finally trumps.
上面代碼可知如果未出現(xiàn)異常時是順序執(zhí)行 try 和 finally 代碼塊
-
當(dāng) try 和 catch 中有 return 時冶忱,finally 仍然會執(zhí)行
public static int demo2() { try { return0; } finally { System.out.println("finally trumps return."); } } //輸出結(jié)果 finally trumps return. 0
上面代碼可知當(dāng) finally 里面沒有 return 語句時,執(zhí)行 try 和 finally 語句之后最后再執(zhí)行 return
-
finally 是在 return 后面的表達式運算后執(zhí)行的(此時并沒有返回運算后的值境析,而是先把要返回的值保存起來囚枪,管 finally 中的代碼怎么樣派诬,返回的值都不會改變,任然是之前保存的值)链沼,所以函數(shù)返回值是在 finally 執(zhí)行前確定的
public static int demo3() { int i = 0; try { i = 2; return i; } finally { i = 12; System.out.println("finally trumps return."); } } //輸出結(jié)果 finally trumps return. 2
此處中 finally 中對 i 賦值12但是 demo3 的返回值仍然是2默赂,也就是在 finally 中對i賦值并未改變i的返回值,這里需要詳細的講一下括勺,此處涉及到了jvm機制:
image.png -
在variable內(nèi)存中有兩個變量區(qū)域一個是用來存放i的值缆八,對應(yīng)最上面的那個,另一個用于存放返回值疾捍。在上面代碼執(zhí)行到 i = 2; return i ;先對 i 賦值2奈辰,然后執(zhí)行 return 語句此時并不是將結(jié)果返回,而是將 i = 2 的值保存到返回值變量區(qū)域乱豆,在執(zhí)行完 i = 12 時奖恰,再返回 variable 中返回值地址變量區(qū)域的2
-
finally中最好不要包含return,否則程序會提前退出宛裕,返回值不是try或catch中保存的返回值
public static int demo4() { int i = 0; try { return i; } finally { i = 12; System.out.println("finally trumps return."); return i; } } //輸出結(jié)果 finally trumps return. 12
在程序還未執(zhí)行try中的 return 語句時就先執(zhí)行了 finally 里面的 return 語句瑟啃,所以返回結(jié)果為12