Java基礎(24)——異常詳解
版權聲明
- 本文原創作者:清風不渡
- 博客地址:https://blog.csdn.net/WXKKang
一、異常體系
1、概述
異常,即不正常;在軟件開發領域它指程序發生了不正常的情況
每種異常都是使用一個異常對象來表示,所有的異常類都是直接或間接的繼承自Throwable類,它們組成了一個完整的類繼承結構,其中根類是Throwable,在Java中,異常體系中的類在java.lang中定義,這個包是默認被導入的
2、異常的根類(Throwable)
(1)構造函數
(2)主要方法
getMessage()方法
輸出異常信息,實際上就是通過構造方法傳入的字符串,例如:
package cn.com;
public class Demo {
public static void main(String[] args) {
Throwable throwable = new Throwable("這是一串異常消息");
System.out.println(throwable.getMessage());
}
}
執行結果如下:
toString()方法
以字符串形式顯示異常,如下:
package cn.com;
public class Demo {
public static void main(String[] args) {
Throwable throwable = new Throwable("這是一串異常消息");
System.out.println(throwable.toString());
}
}
執行結果如下:
printStackTrace()方法
打印異常消息和異常棧,有以下三種使用方法:
方法 | 註釋 |
---|---|
printStackTrace() | 將異常信息輸出到控制檯 |
printStackTrace(PrintStream) | 將異常信息輸出到一個字節流中 |
printStackTrace(PrintWriter) | 將異常信息輸出到一個字符流中 |
3、錯誤(Error)
這類問題是有Java虛擬機拋出的,一般都是很嚴重的,比如說系統崩潰、內存溢出、加載動態鏈接庫失敗、棧溢出等等,這類問題是我們在應用程序的層面無法處理和解決的,常見的如:StackOverflowError,OutOfMemory(OOM)
4、Exception
Exception分爲兩種:
運行時異常(RuntimeException)
RuntimeException類及其子類都是程序可以通過編譯;但存在安全隱患;爲了避免這種情況的發生建議提高代碼的健壯性;在必要時刻也可採用try…catch處理,以下是常見的編譯時異常:
編譯時異常
除了RuntimeException以外Exception的其他子類都屬於編譯時異常。這種異常必須要處理,否則將不能通過編譯,以下是幾種常見的編譯時異常:
二、異常的處理方式
1、默認的異常處理方式
我們知道,在做除法運算時,除數是不能等於零的,現在我們就利用這一特點來製造一個異常,看看Java默認的處理異常的方式是怎樣的,代碼如下:
package cn.com;
public class Demo {
public static void main(String[] args) {
int a =5;
int b =0;
System.out.println("a的值爲"+a+",b的值爲"+b);
int c=a/b;
System.out.println("a除以b的結果爲:"+c);
}
}
執行結果如下:
如上代碼所述,當除數爲零的時候,將發生算術異常,因此代碼System.out.println(“a除以b的結果爲:”+c);將不會被執行
那麼,我們是否可以自行的處理異常呢,答案是可以的,有如下幾種方式:
2、try…catch方式
(1)基本知識
try…catch語法如下:
//執行try裏面的代碼,如果有異常則跳至catch中執行其內的代碼
try{
可能出現問題的代碼;
}catch(異常名 變量){
針對問題的處理;
}finally{
釋放資源;
}
//或者
try{
可能出現問題的代碼;
}catch(異常名 變量){
針對問題的處理;
}
注意:
第一點:try裏面的代碼越少越好
第二點:catch裏面必須要有內容,哪怕是給出一個簡單的提示
例子: 下面我們就將上面的例子用try…catch方式來自行的處理異常,代碼如下:
package cn.com;
public class Demo {
public static void main(String[] args) {
int a =5;
int b =0;
try {
System.out.println("a除以b的結果爲:"+a/b);
} catch (ArithmeticException e) {
if (b==0) {
System.out.println("除數不能爲零,請重新輸入");
}
}
System.out.println("除法運算結束!!!");
}
}
執行結果如下:
如果沒有使用try…catch進行處理,那麼出現算術異常之後,程序就會異常終止,而使用try…catch處理異常之後,程序會繼續執行try…catch之後的代碼,因此會執行System.out.println(“除法運算結束!!!”);
如果代碼中可能拋出多種異常 ,例如除數爲零、數組下標越界、空指針等在一起該如何處理呢?只需要我們將可能發生異常的代碼放入try語句中,然後爲其添加多個catch語句,每個catch語句中處理一種異常即可
總結:
1、程序中的功能代碼放在try塊中,這些功能代碼中可以有多個語句可能發生異常。
2、在try塊中的代碼拋出的異常後,這行代碼之後的try 塊中的代碼都將不會再執行,現在將查找匹配的catch塊繼續處理。
3、JVM將會按catch塊的代碼順序依次匹配異常類型。如果有任何一個catch塊被匹配,那麼其它的catch將不會再次被匹配。在catch塊代碼執行完成後,執行try/catch塊後面的代碼。
如果沒有匹配任何一個catch塊,那麼異常將被拋出到函數外面,參見(3)小節內容。
如果有任何一個catch被匹配,那麼拋出的異常對象將被賦給catch中定義的異常變量。例如,catch (NullPointerException e)將異常對象賦給e。
(2)使用多態機制處理異常
前面的例子中,如果try塊中可能拋出多個異常,則需要爲每個異常都創建一個catch塊;這樣的話,當拋出的異常類型比較多的時候,catch塊就會很多,這樣代碼就會比較臃腫且不易維護,那麼怎麼辦呢?有以下兩種方式:
a、使用父類捕獲
在實際項目中,很多時候並不需要詳細區分每種異常類型,這時希望提供某種簡化處理方式,使一個catch塊可以捕獲多種異常,我們就可以使用多態的特性來解決這個問題,使用這些異常的父類接收,例如:
try {
...
} catch (Exception e) {
...
}
現在,catch(Exception e)分支將可以匹配Exception及其所有的子類,下面就來舉例說明:
package qfbd.com;
public class Demo {
public static void main(String[] args) {
int a = 5;
int b = 0;
int[] arr = {1,2};
Demo demo = new Demo();
demo.demo1(a, b, arr);
}
public void demo1(int a,int b,int[] arr){
try {
System.out.println(arr[5]); //數組下標越界
System.out.println(a/b); //除數爲零
} catch (Exception e) {
// TODO: handle exception
System.out.println("出錯啦");
}
System.out.println("函數執行完畢");
}
}
執行結果:
從上面的例子中我們可以看出,因爲所有的異常類都是Exception類的子類,所以當使用父類捕獲異常之後,所有在try{}中發生的異常都會被捕獲而顯示出catch (Exception e) {}中的異常信息,這就是父類捕獲
b、同時使用父類和子類捕獲
通常,我們會使用父類來匹配捕獲它本身和它所有的子類,這樣代碼就會比較簡潔。但是有時候我們需要單獨的處理某個具體的子類,那麼又該這麼辦呢?我們可以同時使用父類和子類捕獲異常來實現這一需求,例如:
package qfbd.com;
public class Demo {
public static void main(String[] args) {
int a = 5;
int b = 0;
int[] arr = {1,2};
Demo demo = new Demo();
demo.demo1(a, b, arr);
}
public void demo1(int a,int b,int[] arr){
try {
System.out.println(arr[5]); //數組下標越界
System.out.println(a/b); //除數爲零
} catch (ArrayIndexOutOfBoundsException e) {
// TODO: handle exception
e.toString();
e.getMessage();
e.printStackTrace();
System.out.println("數組下標越界異常");
}catch (Exception e) {
// TODO: handle exception
System.out.println("出錯啦");
}
System.out.println("函數執行完畢");
}
}
執行結果:
看到上面的代碼我們或許有這樣一個疑問,那就是在try{}中還有一個算術異常(除數爲零),它在下方的catch中對應的應該是其父類的異常信息,但是它並沒有執行,是因爲當順序執行到數組下標越界的語句時發生了異常,從而直接跳到了catch (ArrayIndexOutOfBoundsException e) {}中,而該句後面的語句都沒有被執行,所以才只會顯示出了數組下標越界異常。所以若要實現單獨的處理某個異常信息,則需要滿足以下的語法格式:
多個catch語句之間的執行順序
1、catch塊是按照代碼順序進行匹配,從上到下
2、如果多個catch內的異常有父子類關係。那麼要按照子類在前,父類在後的順序編寫
如果子類異常在前,父類在後,那麼編譯沒有問題
如果父類異常在前,子類在後,則編譯不通過。因爲父類可以處理子類的異常,那麼子類的catch將執行不到
(3)finally
在ctr…catch之後我們通常會加上一個finally,在其內進行資源的清除工作,如關閉打開的文件等。在處理異常的時候最多只能有一個finally語句塊,但是在try…carch中可以沒有finally語句塊
(1)通常使用finally的作用——在finally中進行資源回收
例如:在程序中打開了一個文件,在try中捕獲到了一個異常,使用catch處理後程序正常執行
package qfbd.com;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
demo.demo1();
}
public void demo1(){
InputStream fis = null;
try {
fis = new FileInputStream("d:/test.txt");
int a = 5/0; //除數爲零
} catch (Exception e) {
// TODO: handle exception
System.out.println("出錯啦");
}
System.out.println("函數執行完畢");
}
}
執行結果如下:
由於我們沒有及時的關掉io流,並且JVM中使用垃圾回收機制來收回不再使用的對象,所以這裏的InputStream對象將由垃圾回收線程來回收,但是垃圾回收的執行時間是不確定的,因此,在上面的代碼中我們將無法及時回收爲讀取文件而分配的資源
爲了解決這個問題,Java中引入了 finally語句塊,在try/catch執行完成後,其後的finally塊一定會得到執行,這樣就可以在finally中執行資源釋放工作,例如:
package qfbd.com;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
demo.demo1();
}
public void demo1(){
InputStream fis = null;
try {
fis = new FileInputStream("d:/test.txt");
int a = 5/0; //除數爲零
} catch (Exception e) {
// TODO: handle exception
System.out.println("出錯啦");
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("函數執行完畢");
}
}
這樣就會在出現異常並執行catch塊語句之後第一時間來關閉開啓的資源,從而達到資源及時釋放的目的
(2)finally是否永遠都執行?
a、無論程序正常還是異常,都會執行finally
b、return都不能停止finally的執行過程,例如:
try {
return;
} finally {
// 即使try塊中執行了return,finally中的代碼也會在函數返回前執行
}
c、只有一種情況可以阻止finally塊的執行——如果JVM退出了(System.exit(0)),finally就不會執行
try {
System.exit(0);
} finally {
// 這種情況下finally中的代碼將不會執行
}
3、throws
1、基礎知識及示例
處理異常的時候,除了try…catch方式之外還有一種方式,那就是將異常拋出,我們利用throws可以將異常拋出本函數,如果我們不想在本函數中處理異常,或是處理不了,就可以利用它將異常拋出到函數的外部
語法格式:
throws+異常類名
(1)在demo1函數中聲明異常並拋出
例如,在函數demo1(int a,int b)中,如果除數(b)爲0,使用throw new Exception(“除數不能爲0”);拋出Exception類型的異常,那麼在函數聲明上使用throws聲明拋出的異常,告知函數調用者必須處理。如果在函數的聲明上不使用throws聲明拋出異常類型,編譯將會報錯
如下,有一個函數中使用了throws的方法拋出了一個異常
public void demo1(int a,int b) throws Exception{ //聲明異常,告知調用者必須處理
if(b==0){
throw new Exception("除數不能爲零"); //throw關鍵字後跟的是具體的異常對象
}
System.out.println("ab相除的結果爲:"+a/b);
System.out.println("除法運算");
}
(2)在函數中調用demo1函數
當一個函數調用這個函數的時候,編譯時會報錯,提示需要處理這個異常,如下:
我們可以選擇繼續拋出異常,或是使用try…catch的方式捕獲這個異常,如下:
//try...catch方式捕獲異常
public void test (){
int a = 6;
int b = 0;
Demo demo = new Demo();
try {
demo.demo1(a, b);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//throws方式拋出異常
public void test () throws Exception{
int a = 6;
int b = 0;
Demo demo = new Demo();
demo.demo1(a, b);
}
(3)使用main函數調用demo1函數
當main函數調用這個含有拋出異常的函數時,我們也可以選擇使用throws的方式拋出異常或是使用try…catch的方式捕獲異常,但是需要注意的一點是:當在main函數中使用throws拋出異常時,如果在運行過程中不論發生並拋出了任何異常,都會導致JVM異常中止!!!
package qfbd.com;
public class Demo {
public static void main(String[] args) throws Exception {
int a = 6;
int b = 0;
Demo demo = new Demo();
demo.demo1(a,b);
System.out.println("over");
}
public void demo1(int a,int b) throws Exception{ //聲明異常,告知調用者必須處理
if(b==0){
throw new Exception("除數不能爲零"); //throw關鍵字後跟的是具體的異常對象
}
System.out.println("ab相除的結果爲:"+a/b);
System.out.println("除法運算");
}
}
(4)throw和throws的區別
a、相同點
兩者都是用於做異常的拋出處理的
b、不同點
使用的位置不同:在函數上使用throws聲明被拋出的異常類型,在函數內使用throw拋出異常對象
c、後面接受的內容不同
throws後是拋出的異常類型,可以有多個,使用逗號隔開。例如:
//throws後是拋出的異常類型
public void demo2() throws IOException,FileNotFoundException {
//throw後是一個被拋出的異常對象
throw new IOException("IO異常");
}
2、常見問題
(1)子類的方法重寫
我們知道,當一個子類繼承了一個父類的時候,便繼承了父類的方法,如果子類中重寫了父類的方法,並且父類中對應的方法有使用throws拋出異常的行爲,則需要注意:
子類中重寫方法中拋出的異常類型不能大於父類中被重寫方法拋出的異常類型,否則會編譯失敗,例如:
總結:
1、父類方法拋出異常,子類的重寫方法可以不拋出異常,或者拋出父類方法的異常,或者該父類方法異常的子類
2、父類沒有拋出異常,則子類不可拋出異常
(2)Throwable
如果在程序中希望捕獲所有的異常或錯誤,需使用Throwable進行捕獲。這種結構通常是在服務器中使用的,爲了保證服務器中發生的任何異常都不會拋出到JVM,從而導致JVM異常終止,語法如下:
try {
} catch (Throwable th) {
// 異常處理
}
4、異常總結
Java中的異常處理是通過三個關鍵字來實現的:try——catch——finally,語法如下:
try {
//接收監視的程序塊,在此區域內發生的異常,有catch中指定的程序處理
} catch (要處理的異常類型 標識符) {
// 處理異常
}catch (要處理的異常類型 標識符) {
// 處理異常
}
...
finally {
//最終處理
}
2、try、catch、finally 這三個關鍵字均不能單獨使用,可以組成try…catch、 try…finally、try…catch…finally三種結構。
3、catch 語句可以有一個或多個,finally 語句最多-一個。
4、try、 catch、 finally 三個代碼塊中變量的作用域分別獨立而不能相互訪問。如果要在三個塊中都可以訪問,則需要將變量定義到這些塊的外面。
5、有多個catch塊時候,如果匹配就僅僅執行這個catch塊,不會再執行別的catch塊。
6、throw語句後不允許緊跟其他語句,因爲這些語句沒有機會執行,編譯時會報錯。
7、如果一個方法調用了另外一個聲明拋出異常的方法,那麼這個方法要麼處理異常,要麼重新聲明拋出。
8、有多個catch 塊時,如果其中異常類型有父子類關係時,則子類在前面,父類在後面。
三、自定義異常類
通常,自定義異常繼承於Exception 類。自定義異常類名的命名規則是“異常名字+Exception”,例如: NoMoneyException。 .
例子,在支付賬單時沒有足夠的錢:
這是一種典型的業務異常,我們需要定製一個繼承於Exception 的NoMoneyException,在這個異常類中提供二種構造函數:有參構造函數和無參構造函數。
class NoMoneyException extends Exception{
public NoMoneyException() {
// TODO Auto-generated constructor stub
}
public NoMoneyException(String message) {
// TODO Auto-generated constructor stub
super(message);
}
}
注意:通常自定義異常類就是這種類型的結果,只需要簡單的定義。我們的目的是在catch語句中使用異常類型對不同的錯誤進行分類,並不需要異常類型提供其他的功能。
在應用程序中,直接創建異常對象並拋出,然後按照常規的異常處理編程,例如:
如果money小於10元,則拋出異常,提示餘額不足,如下:
package qfbd.com;
public class Demo {
public static void main(String[] args) throws Exception {
double money = 5;
pay(money);
}
public static void pay(double money) throws NoMoneyException{
if(money<10){
throw new NoMoneyException("餘額不足,請充值");
}
System.out.println("支付成功!!");
}
}
class NoMoneyException extends Exception{
public NoMoneyException() {
// TODO Auto-generated constructor stub
}
public NoMoneyException(String message) {
super(message);
}
}
執行結果:
如果money大於10元,則顯示支付成功,如下:
package qfbd.com;
public class Demo {
public static void main(String[] args) throws Exception {
double money = 20;
pay(money);
}
public static void pay(double money) throws NoMoneyException{
if(money<10){
throw new NoMoneyException("餘額不足,請充值");
}
System.out.println("支付成功!!");
}
}
class NoMoneyException extends Exception{
public NoMoneyException() {
// TODO Auto-generated constructor stub
}
public NoMoneyException(String message) {
super(message);
}
}
執行結果: