# Java異常
[TOC]
## 導學
本節課程,我們將會學習到異常,以及如何處理可能會發生的異常。
首先,什么是異常?異常可以理解為意外,例外的意思,本質上是程序出現的錯誤。錯誤在我們編寫程序的過程中經常會發生,包括編譯期間和運行期間的錯誤。比如括號沒有正常的配對,語句少寫了分號,關鍵字編寫錯誤等就是編譯期間會出現的錯誤。通常這些**編譯錯誤編譯器會幫助我們進行修訂。**
那么,運行期間的錯誤,我們也曾遇到過。比如使用空的對象引用調用方法、數組訪問時下標越界、算數運算除數為0、類型轉換時無法正常轉型等,這些錯誤在編譯的時候完全沒有提示。
>[info]在程序運行過程中,意外發生的情況,背離我們程序本身的意圖的表現,都可以理解為異常。
那么,為什么會出現異常呢?
>[danger]用戶不正當的輸入
本身系統代碼編寫的問題
如果不對異常進行處理,那么輕則數據錯誤、丟失,重則程序崩潰。比如下面這段代碼
~~~Java
public class ExceptionStudy {
public static void main(String[] args) {
System.out.println("******程序開始運行*******");
System.out.println("******數學計算:"+ (10/2) +"*******");
System.out.println("******程序運行結束*******");
}
}
運行結果:
******程序開始運行*******
******數學計算:5*******
******程序運行結束*******
public class ExceptionStudy {
public static void main(String[] args) {
System.out.println("******程序開始運行*******");
System.out.println("******數學計算:"+ (10/0) +"*******");
System.out.println("******程序運行結束*******");
}
}
運行結果:
******程序開始運行*******
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.ntdodoke.preparation.usuallyClass.ExceptionStudy.main(ExceptionStudy.java:6)
~~~
在出現異常的地方,程序就會被「異常指令流」直接終止,不再往下運行。為了讓程序在出現異常后依然可以正常執行,所以我們必須正確處理異常來完善代碼的編寫。
在Java中,提供了一種強大的異常處理機制來幫助我們實現異常的處理
## 異常介紹

>[info]Error是程序無法處理的錯誤,表示運行應用程序中較為嚴重的問題,是代碼運行時Java虛擬機中出現的問題。這種錯誤是不可查的,它們在應用程序的控制和處理能力之外,而且絕大多數是程序運行時不允許出現的狀況。
對于設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。
>[danger]Excepetion是程序本身可以處理的異常。異常處理通常針對這種類型異常的作出處理 。`RuntimeException`是非檢查異常,也就是編譯器不要求強制處理的異常。程序員可以針對這些異常進行捕獲或放任不管。檢查異常包括IO異常和SQL異常,編譯器要求必須在代碼中處理這些異常。
## 異常處理
針對于異常處理,通常我們最簡單的方式就是思考全面,檢查代碼。將可能會發生的錯誤預先考慮透徹。那么也有可能會出現一些無法預料到的情況,我們就可以使用到Java的異常處理機制。在Java中異常處理機制分為兩種:拋出異常和捕獲異常。
拋出異常指的是一個方法中出現錯誤引發的異常時,方法會創建異常對象,交給運行時系統進行處理。在異常對象中包含異常類型,異常出現時的程序狀態等。運行時系統捕獲到這個異常后,進入捕獲異常環節,運行時系統會找合適的處理器,與拋出異常匹配后進行處理,如果沒找到則程序終止。Java規定,對于檢查異常必須捕獲、或者聲明拋出。對于非檢查異常(RuntimeException及其子類)和Error及其子類,允許忽略。
拋出異常、捕獲異常通過5個關鍵字實現:try、catch、finally、throw、throws。try、catch、finally通常用來捕獲異常,throws通常用來聲明異常,throw通常用來拋出異常。

### try...catch...finally
語法:
~~~
try {
//用于捕獲異常
可能會發生異常的代碼塊;
} catch(Exception e) {//異常類型
//用于處理try中捕獲的異常
} finally {
//無論是否發生異常都會執行的代碼
}
~~~
**try塊后可接零個或多個catch塊,如果沒有catch塊,則必須跟一個finally塊。** 也就是說try必須和catch,finally組合使用。catch、finally也不允許單獨存在。
案例:
~~~java
public class TryDemo {
public static void main(String[] args) {
//定義兩個整數,輸出兩個數的商
Scanner input = new Scanner(System.in);
System.out.println("======運算開始=======");
try{
System.out.println("請輸入第一個整數");
int one = input.nextInt();
System.out.println("請輸入第二個整數");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
}catch(Exception e) {//這是對異常種類的猜測,通常可以采用異常的父類,就不用猜測具體是哪個子類異常了
e.printStackTrace();//在控制臺打印出異常種類,錯誤信息和出錯位置等,輸出位置比較隨機
System.out.println("程序出錯了~~~");
} finally {
System.out.println("運算結束");
}
}
}
~~~
在Java中,可以使用多重catch語句塊,針對于同一塊代碼可能發生的不同種類異常作出處理。比如上述代碼可能會發生算術異常也可能會發生其他異常。但是兩個catch塊中對于異常的猜測是不允許相同的,而且對于父類的異常是必須要放置在子類的異常catch代碼塊下面的。兄弟異常的catch代碼塊位置可以隨意。
~~~
public class TryDemo {
public static void main(String[] args) {
//定義兩個整數,輸出兩個數的商
Scanner input = new Scanner(System.in);
System.out.println("======運算開始=======");
try{
System.out.println("請輸入第一個整數");
int one = input.nextInt();
System.out.println("請輸入第二個整數");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
} catch(InputMismatchException a) {//輸入格式異常
System.out.println("請輸入整數!");
a.printStackTrace();
} catch(ArithmeticException b) {//數學運算異常
System.out.println("除數不能為0!");
b.printStackTrace();
} catch(Exception e) {//這是對異常種類的猜測,通常可以采用異常的父類,就不用猜測具體是哪個子類異常了
e.printStackTrace();//在控制臺打印出異常種類,錯誤信息和出錯位置等,輸出位置比較隨機
System.out.println("程序出錯了~~~");
} finally {
System.out.println("運算結束");
}
}
}
~~~
我們會發現,最終輸出的異常信息,是我們猜測的某一個異常。同樣我們不可能猜測出所有的異常信息,所以需要在catch代碼塊的最下面寫出一個父類異常catch代碼塊,以免發生遺漏!
#### 終止finally執行的方法
在之前的課程中,我們可以看到,finally語句塊中的內容無論怎樣都會運行,那么有沒有一種方法可以終止finally中方法的運行呢
~~~
public class TryDemo {
public static void main(String[] args) {
//定義兩個整數,輸出兩個數的商
Scanner input = new Scanner(System.in);
System.out.println("======運算開始=======");
try{
System.out.println("請輸入第一個整數");
int one = input.nextInt();
System.out.println("請輸入第二個整數");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
} catch(InputMismatchException a) {//輸入格式異常
System.exit(1);//終止程序運行
System.out.println("請輸入整數!");
a.printStackTrace();
} catch(ArithmeticException b) {//數學運算異常
System.out.println("除數不能為0!");
b.printStackTrace();
} catch(Exception e) {//這是對異常種類的猜測,通常可以采用異常的父類,就不用猜測具體是哪個子類異常了
e.printStackTrace();//在控制臺打印出異常種類,錯誤信息和出錯位置等,輸出位置比較隨機
System.out.println("程序出錯了~~~");
} finally {
System.out.println("運算結束");
}
}
}
~~~
`System`中存在`exit`方法,該方法的作用可以用來終止Java虛擬機的運行,傳入的參數是數字,這個數字只要不是數字0描述的都是異常終止狀態。可以使用該方法來使當前程序無條件終止運行。
#### return關鍵字在異常處理中的作用
在之前的學習中,我們知道return可以用來提供方法的返回值。那么return是否可以用在異常處理中呢,或者又有什么作用呢
~~~
public class TryTest {
public static void main(String[] args) {
int result = test();
System.out.println("運行的結果為:" + result);
}
public static int test() {
Scanner input = new Scanner(System.in);
System.out.println("======運算開始=======");
try{
System.out.println("請輸入第一個整數");
int one = input.nextInt();
System.out.println("請輸入第二個整數");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
return one / two;
} catch(ArithmeticException b) {
System.out.println("除數不能為0!");
b.printStackTrace();
return 0;
} finally {
System.out.println("運算結束");
return -10000;
}
}
}
~~~
當try,catch,finally三個語句塊中都存在return關鍵字時,無論程序運行正常不正常,最終的結果都是-10000;原因在于finally無論如何都會執行。
### throw和throws
#### 使用throws聲明異常類型
可以通過throws聲明將要拋出何種類型的異常,通過throw將產生的異常拋出。如果一個方法可能會出現異常,但沒有能力處理這種異常,可以在方法聲明處用throws子句來聲明拋出異常。誰調用這個方法則誰處理拋出的異常。
throws 后面可以跟多個異常類型,多個異常使用逗號隔開。當方法拋出異常時,方法將不對這些類型及其子類類型異常做處理,而拋向調用該方法的方法,由他去處理。
~~~
public class TryTest {
public static void main(String[] args) {
try {
int result = test();
System.out.println("運行的結果為:" + result);
} catch(ArithmeticException e) {
System.out.println("要求除數不能為0");
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
//test();寫exception異常后會有提示
}
public static int test() throws ArithmeticException, Exception{//告訴編譯器該方法可能會發生何種異常,可以使用多異常或寫Exception
Scanner input = new Scanner(System.in);
System.out.println("======運算開始=======");
System.out.println("請輸入第一個整數");
int one = input.nextInt();
System.out.println("請輸入第二個整數");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
System.out.println("運算結束");
return one / two;
}
}
~~~
如果方法拋出的是非檢查異常,那么調用此方法處編譯器不會強制要求進行異常處理(不會報錯),如果方法拋出的異常是檢查異常或者Exception,則調用此方法處編譯器會強制要求進行異常處理(不處理會報錯)。針對不報錯的情況,可以在拋出異常的方法處加文檔注釋,這樣調用此方法處雖然編譯器不會提示要異常處理,但是文檔注釋會提示。
#### throw拋出異常對象
注意,throw拋出的是異常對象。throw拋出的只能是Throwable或者是其子類的實例對象。
~~~
用throws往上拋
public class TryTest {
//酒店入住規則:18以下和80歲以上必須有親友陪同,不得單獨入住
public static void testAge() throws Exception {
System.out.println("請輸入年齡:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
if(age < 18 || age > 80) {
throw new Exception("18歲以下,80歲以上的住客必須由親友陪同入住");
} else {
System.out.println("歡迎入住本酒店!");
}
}
}
自己拋出自己處理
public class TryTest {
//酒店入住規則:18以下和80歲以上必須有親友陪同,不得單獨入住
public static void testAge() {
System.out.println("請輸入年齡:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
if(age < 18 || age > 80) {
try {
throw new Exception("18歲以下,80歲以上的住客必須由親友陪同入住");
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("歡迎入住本酒店!");
}
}
}
~~~
throw拋出異常對象的處理方案:
1. 自己拋出自己處理,通過try-catch包含throw語句。
2. 用throws往上拋,調用者可以try-catch處理或者繼續往上拋。throws拋出異常類型時,要拋出與throw對象相同的類型或者其父類,但不能是子類
PS:**throw手動拋出的異常不建議使用非檢查類型,因為編譯器不提示**
## 自定義異常
使用Java內置的異常類可以描述在編程時出現的大部分異常情況。也可以通過自定義異常描述特定業務產生的異常類型。所謂自定義異常,就是定義一個類,去繼承Throwable類或者它的子類。
比如,在之前酒店的例子中,自己新設置的異常信息被多次使用,就可以創建一個異常類
~~~
public class HotelAgeException extends Exception{
public HotelAgeException (){
super("18歲以下住店必須由親友陪同");
}
}
~~~
>[info]getMessage()方法可以顯示異常信息
## 異常鏈
~~~
public class TryClient {
public static void main(String[] args) {
try {
testThree();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testOne() throws HotelAgeException {
throw new HotelAgeException();
}
public static void testTwo() throws Exception {
try {
testOne();
} catch(HotelAgeException e) {
throw new Exception("我是新產生的異常1",e);//java中保留異常的機制
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch(Exception e) {
//throw new Exception("我是新產生的異常2",e);或寫成另一種方式
Exception e1 = new Exception("我是新產生的異常2");
e1.initCause(e);
throw e1;
}
}
}
~~~
異常鏈就是一個異常接著一個異常。最后輸出的異常就只有最后一個。
如果想要把前面所有異常都輸出的話,則需要通過保留異常的方法:
1. 通過構造方法對舊異常對象的獲取
Throwable(String message, Throwable cause) -- 保留底層異常的異常信息。
2. 通過initCause(Throwable cause)方法(一個異常的信息來初始化一個新的異常)用來獲取原始異常的描述信息,其中cause是原始異常的對象
## 練習
一、選擇
1. 下列代碼中的異常屬于(多選)

~~~
A. 非檢查型異常
B. 檢查型異常
C. Error
D. Exception
~~~
2. 類及其子類所表示的異常是用戶程序無法處理的
~~~
A. NumberFormatException
B. Exception
C. Error
D. RuntimeException
~~~
3. 數組下標越界,則發生異常,提示為
~~~
A. IOException
B. ArithmeticException
C. SQLException
D. ArrayIndexOutOfBoundsException
~~~
4. 運行下列代碼,當輸入的num值為a時,系統會輸出

~~~
A. one three end
B. two three end
C. one two three end
D. two end
~~~
5. 運行下列代碼,輸出結果為

~~~
A. a = 0
B. a = 0
除數不允許為0
C. a = 1
數組越界
D. a = 0
除數不允許為0
數組越界
~~~
6. 下列關于異常的描述,錯誤的是(多選)
~~~
A. printStackTrace()用來跟蹤異常事件發生時執行堆棧的內容
B. catch塊中可以出現同類型異常
C. 一個try塊可以包含多個catch塊
D. 捕獲到異常后將輸出所有catch語句塊的內容
~~~
7. 假設要輸入的id值為a101,name值為Tom,程序的執行結果為

~~~
A. id=a101
name=Tom
B. id=a101
name=Tom
輸入結束
C.【輸入數據不合規范。Try again】
D.【輸入數據不合規范。Try again】
輸入結束
java.util.InputMismatchException...
~~~
8. 下列代碼的運行結果為

~~~
A. 1
B. 10
C. 20
D. 30
~~~
9. 在下列代碼劃線處不可以填入選項中的哪一個異常類型

~~~
A. Throwable
B. Exception
C. InputMismatchException
D. ArithmeticException
~~~
10. 假設有自定義異常類MyException,那么拋出該異常的語句正確的是
~~~
A. throw new Exception()
B. throw new MyException()
C. throw MyException
D. throws Exception
~~~