JavaSE學習筆記(6) 異常 軟體程式在運行過程中,非常可能遇到問題,我們稱之為異常,英文是:Exception,意思是例外。遇到這些例外情況,或者叫異常,我們怎麼讓寫的程式做出合理的處理,安全的退出,而不至於程式崩潰呢? 1.異常產生的原因及使用原則 在 Java 中一個異常的產生,主要有 ...
JavaSE學習筆記(6)---異常
軟體程式在運行過程中,非常可能遇到問題,我們稱之為異常,英文是:Exception,意思是例外。遇到這些例外情況,或者叫異常,我們怎麼讓寫的程式做出合理的處理,安全的退出,而不至於程式崩潰呢?
1.異常產生的原因及使用原則
在 Java 中一個異常的產生,主要有如下三種原因:
- Java 內部錯誤發生異常,Java 虛擬機產生的異常。
- 編寫的程式代碼中的錯誤所產生的異常,例如空指針異常、數組越界異常等。這種異常稱為未檢査的異常,一般需要在某些類中集中處理這些異常。
- 通過 throw 語句手動生成的異常,這種異常稱為檢査的異常,一般用來告知該方法的調用者一些必要的信息。
Java 通過面向對象的方法來處理異常。在一個方法的運行過程中,如果發生了異常,則這個方法會產生代表該異常的一個對象,並把它交給運行時的系統,運行時系統尋找相應的代碼來處理這一異常。
我們把生成異常對象,並把它提交給運行時系統的過程稱為拋出(throw)異常。運行時系統在方法的調用棧中查找,直到找到能夠處理該類型異常的對象,這一個過程稱為捕獲(catch)異常。
Java 異常強制用戶考慮程式的強健性和安全性。異常處理不應用來控製程序的正常流程,其主要作用是捕獲程式在運行時發生的異常併進行相應處理。編寫代碼處理某個方法可能出現的異常,可遵循如下三個原則:
- 在當前方法聲明中使用 try catch 語句捕獲異常。
- 一個方法被覆蓋時,覆蓋它的方法必須拋出相同的異常或異常的子類。
- 如果父類拋出多個異常,則覆蓋方法必須拋出那些異常的一個子集,而不能拋出新異常。
2.異常的類型
在 Java 中所有異常類型都是內置類 java.lang.Throwable 類的子類,即 Throwable 位於異常類層次結構的頂層。Throwable 類下有兩個異常分支 Exception 和 Error,如圖所示。
Throwable 類是所有異常和錯誤的父類,下麵有 Error 和 Exception 兩個子類分別表示錯誤和異常。其中異常類 Exception 又分為運行時異常和非運行時異常,這兩種異常有很大的區別,也稱為不檢查異常(Unchecked Exception)和檢查異常(Checked Exception)。
- Exception 類用於用戶程式可能出現的異常情況,它也是用來創建自定義異常類型類的類。
- Error 定義了在通常環境下不希望被程式捕獲的異常。Error 類型的異常用於 Java 運行時由系統顯示與運行時系統本身有關的錯誤。堆棧溢出是這種錯誤的一例。
- 本章不討論關於 Error 類型的異常處理,因為它們通常是災難性的致命錯誤,不是程式可以控制的。本章接下來的內容將討論 Exception 類型的異常處理。
運行時異常都是 RuntimeException 類及其子類異常,如 NullPointerException、IndexOutOfBoundsException 等,這些異常是不檢查異常,程式中可以選擇捕獲處理,也可以不處理。這些異常一般由程式邏輯錯誤引起,程式應該從邏輯角度儘可能避免這類異常的發生。
非運行時異常是指 RuntimeException 以外的異常,類型上都屬於 Exception 類及其子類。從程式語法角度講是必須進行處理的異常,如果不處理,程式就不能編譯通過。如 IOException、ClassNotFoundException 等以及用戶自定義的 Exception 異常,一般情況下不自定義檢查異常。下表中列出了一些常見的異常類型及它們的作用。
異常類型 | 說明 |
---|---|
Exception | 異常層次結構的根類 |
RuntimeException | 運行時異常,多數 java.lang 異常的根類 |
ArithmeticException | 算術譜誤異常,如以零做除數 |
ArraylndexOutOfBoundException | 數組大小小於或大於實際的數組大小 |
NullPointerException | 嘗試訪問 null 對象成員,空指針異常 |
ClassNotFoundException | 不能載入所需的類 |
NumberF ormatException | 數字轉化格式異常,比如字元串到 float 型數字的轉換無效 |
IOException | I/O 異常的根類 |
F ileN otF oundException | 找不到文件 |
EOFException | 文件結束 |
InterruptedException | 線程中斷 |
IllegalArgumentException | 方法接收到非法參數 |
ClassCastException | 類型轉換異常 |
SQLException | 操作資料庫異常 |
3.異常的處理
1.捕獲異常
捕獲異常是通過3個關鍵詞來實現的:try-catch-finally。用try來執行一段程式,如果出現異常,系統拋出一個異常,可以通過它的類型來捕捉(catch)並處理它,最後一步是通過finally語句為異常處理提供一個統一的出口,finally所指定的代碼都要被執行(catch語句可有多條;finally語句最多只能有一條,根據自己的需要可有可無)。如圖所示。
上面過程詳細解析:
1. try:
try語句指定了一段代碼,該段代碼就是異常捕獲並處理的範圍。在執行過程中,當任意一條語句產生異常時,就會跳過該條語句中後面的代碼。代碼中可能會產生並拋出一種或幾種類型的異常對象,它後面的catch語句要分別對這些異常做相應的處理。
一個try語句必須帶有至少一個catch語句塊或一個finally語句塊 。
註意事項
當異常處理的代碼執行結束以後,不會回到try語句去執行尚未執行的代碼。
2. catch:
n-每個try語句塊可以伴隨一個或多個catch語句,用於處理可能產生的不同類型的異常對象。
n-常用方法,這些方法均繼承自Throwable類 。
u-toString ()方法,顯示異常的類名和產生異常的原因
u-getMessage()方法,只顯示產生異常的原因,但不顯示類名。
u-printStackTrace()方法,用來跟蹤異常事件發生時堆棧的內容。
n-catch捕獲異常時的捕獲順序:
u-如果異常類之間有繼承關係,在順序安排上需註意。越是頂層的類,越放在下麵,再不然就直接把多餘的catch省略掉。 也就是先捕獲子類異常再捕獲父類異常。
2. finally:
n-有些語句,不管是否發生了異常,都必須要執行,那麼就可以把這樣的語句放到finally語句塊中。
n-通常在finally中關閉程式塊已打開的資源,比如:關閉文件流、釋放資料庫連接等。
try-catch-finally語句塊的執行過程:
try-catch-finally程式塊的執行流程以及執行結果比較複雜。
基本執行過程如下:
程式首先執行可能發生異常的try語句塊。如果try語句沒有出現異常則執行完後跳至finally語句塊執行;如果try語句出現異常,則中斷執行並根據發生的異常類型跳至相應的catch語句塊執行處理。catch語句塊可以有多個,分別捕獲不同類型的異常。catch語句塊執行完後程式會繼續執行finally語句塊。finally語句是可選的,如果有的話,則不管是否發生異常,finally語句都會被執行。
註意事項
- 即使try和catch塊中存在return語句,finally語句也會執行。是在執行完finally語句後再通過return退出
- finally語句塊只有一種情況是不會執行的,那就是在執行finally之前遇到了System.exit(0)結束程式運行
典型代碼
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Test8 {
public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader("d:/a.txt");
char c = (char) reader.read();
char c2 = (char) reader.read();
System.out.println("" + c + c2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.拋出異常
當CheckedException產生時,不一定立刻處理它,可以再把異常throws出去。
在方法中使用try-catch-finally是由這個方法來處理異常。但是在一些情況下,當前方法並不需要處理髮生的異常,而是向上傳遞給調用它的方法處理。
如果一個方法中可能產生某種異常,但是並不能確定如何處理這種異常,則應根據異常規範在方法的首部聲明該方法可能拋出的異常。
如果一個方法拋出多個已檢查異常,就必須在方法的首部列出所有的異常,之間以逗號隔開。
註意事項
方法重寫中聲明異常原則:子類重寫父類方法時,如果父類方法有聲明異常,那麼子類聲明的異常範圍不能超過父類聲明的範圍。
典型代碼
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Test9 {
public static void main(String[] args) {
try {
readFile("joke.txt");
} catch (FileNotFoundException e) {
System.out.println("所需文件不存在!");
} catch (IOException e) {
System.out.println("文件讀寫錯誤!");
}
}
public static void readFile(String fileName) throws FileNotFoundException,
IOException {
FileReader in = new FileReader(fileName);
int tem = 0;
try {
tem = in.read();
while (tem != -1) {
System.out.print((char) tem);
tem = in.read();
}
} finally {
in.close();
}
}
}
4.自定義異常
在程式中,可能會遇到JDK提供的任何標準異常類都無法充分描述清楚我們想要表達的問題,這種情況下可以創建自己的異常類,即自定義異常類。
自定義異常類只需從Exception類或者它的子類派生一個子類即可。
自定義異常類如果繼承Exception類,則為受檢查異常,必須對其進行處理;如果不想處理,可以讓自定義異常類繼承運行時異常RuntimeException類。
習慣上,自定義異常類應該包含2個構造器:一個是預設的構造器,另一個是帶有詳細信息的構造器。
示例:
自定義異常:
/**IllegalAgeException:非法年齡異常,繼承Exception類*/
class IllegalAgeException extends Exception {
//預設構造器
public IllegalAgeException() {
}
//帶有詳細信息的構造器,信息存儲在message中
public IllegalAgeException(String message) {
super(message);
}
}
自定義異常的使用:
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) throws IllegalAgeException {
if (age < 0) {
throw new IllegalAgeException("人的年齡不應該為負數");
}
this.age = age;
}
public String toString() {
return "name is " + name + " and age is " + age;
}
}
public class TestMyException {
public static void main(String[] args) {
Person p = new Person();
try {
p.setName("Lincoln");
p.setAge(-1);
} catch (IllegalAgeException e) {
e.printStackTrace();
System.exit(-1);
}
System.out.println(p);
}
}
執行結果:
5.使用異常機制的建議
1.要避免使用異常處理代替錯誤處理,這樣會降低程式的清晰性,並且效率低下。
2.處理異常不可以代替簡單測試---只在異常情況下使用異常機制。
3.不要進行小粒度的異常處理---應該將整個任務包裝在一個try語句塊中。
4.異常往往在高層處理(先瞭解!後面做項目會說!) 。