Java 最全異常講解
1. 導引問題
實際工作中,遇到的情況不可能是非常完美的。比如:你寫的某個模組,使用者輸入不一定符合你的要求、你的程式要開啟某個檔案,這個檔案可能不存在或者檔案格式不對,你要讀取資料庫的資料,資料可能是空的等。我們的程式再跑著,記憶體或硬碟可能滿了。等等。
軟體程式在執行過程中,非常可能遇到剛剛提到的這些異常問題,我們叫異常,英文是:Exception,意思是例外。這些,例外情況,或者叫異常,怎麼讓我們寫的程式做出合理的處理。而不至於程式崩潰。
1.1 常見的錯誤:
- 使用者輸入錯誤
- 裝置錯誤。硬體問題,比如印表機關掉、伺服器問題
- 物理限制。磁碟滿了
- 程式碼限制。陣列下標越界等
設計良好的程式應該在異常發生時提供處理這些錯誤的方法,使得程式不會因為異常的發生而終斷或產生不可預見的結果。
如果沒有異常處理機制,那麼:
1.2 兩個壞處:
1.邏輯程式碼和錯誤處理程式碼放一起!
2.程式設計師本身需要考慮的例外情況較複雜,對程式設計師本身要求較高!
異常機制就是當程式出現錯誤,程式如何安全退出的機制。
2. 異常(Exception)的概念
Java 如何處理異常?
第一個異常示例和解析:
public static void main(String[]args){ int i=1/0; } 執行上述程式碼的異常資訊如下: Exception in thread"main"java.lang.ArithmeticException:/by zero at chuji.BubbleSort.main(BubbleSort.java:11)
Java 是採用面向物件的方式來處理異常的。
3. 處理過程
丟擲異常:在執行一個方法時,如果發生異常,則這個方法生成代表該異常的一個物件,停止當前執行路徑,並把異常物件提交給 JRE。
捕獲異常:JRE 得到該異常後,尋找相應的程式碼來處理該異常。JRE 在方法的呼叫棧中查詢,從生成異常的方法開始回溯,直到找到相應的異常處理程式碼為止。
4. 異常分類
JDK 中定義了很多異常類,這些類對應了各種各樣可能出現的異常事件,所有異常物件都是派生於 Throwable 類的一個例項。如果內建的異常類不能夠滿足需要,還可以建立自己的異常類。
異常類之間的關係圖
圖引用連結:https://www.cnblogs.com/hwaggLee/p/4509038.html
4.2 Error
Error 類層次描述了 Java 執行時系統內部錯誤和資源耗盡錯誤。這類錯誤是我們無法控制的,同時也是非常罕見的錯誤。所以在程式設計中,不去處理這類錯誤。
Error 表明系統 JVM 已經處於不可恢復的崩潰狀態中。我們不需要管他。
開啟 JDK 的:java.lang.error,檢視他的所有子類。
4.3 Exception
所有異常類的父類,其子類對應了各種各樣可能出現的異常事件。
4.4 Error 和 Exception 的區別
我開著車走在路上,一頭豬衝在路中間,我剎車。這叫一個異常。
我開著車在路上,發動機壞了,我停車,這叫錯誤。系統處於不可恢復的崩潰狀態。發動機什麼時候壞?我們普通司機能管嗎?不能。發動機什麼時候壞是汽車廠發動機製造商的事。
4.4.1 Runtime Exception
出現 RuntimeException 就一定是你的問題,可以不捕獲,因為小心點這些異常是可以避免的。 派生於RuntimeException 的異常。是一類特殊的異常,如被 0 除、陣列下標超範圍等,其產生比較頻繁,處理麻煩,如果顯式的宣告或捕獲將會對程式可讀性和執行效率影響很大。因此由系統自動檢測並將它們交給預設的異常處理程式(使用者可不必對其處理)。這類異常通常是由程式設計錯誤導致的,因為只有小心點,這些異常都是可以避免的,所以在編寫程式時,並不要求必須使用異常處理機制來處理這類異常,所有這類異常都繼承自java.lang.RuntimeException。
常見的執行時異常有:
ArithmeticException
如試圖除以0
NullPointerException
當程式訪問一個空物件的成員變數或方法,訪問一個空陣列的成員時發生
ClassCastException
發生多型後,如果強制轉化的並不是父類的子類時發生。編譯的時候可以通過,因為編譯的時候並不會檢查型別轉化的問題
ArrayIndexOutOfBoundsException
訪問的元素下表超過陣列長度
NumberFormatException
數字格式異常!
心得:
大家平時在遇到 NullPointerException,也就是空指標的問題時,不要只記得百度,應該從報錯的地方去分析自己的程式碼,因為空指標其實是你的程式碼寫的不嚴謹造成的。空指標解決方案:Debug,看你對應的值是否為 null。
4.4.2 Checked Exception
所有不是 Runtime Exception 的異常,統稱為 Checked Exception,又被稱為“已檢查異常”。 這類異常的產生不是程式本身的問題,通常由外界因素造成的。為了預防這些異常產生時,造成程式的中斷或得到不正確的結果,Java 要求編寫可能產生這類異常的程式程式碼時,一定要去做異常的處理。
編譯器將檢查是否為所有已檢查異常提供異常處理。
這一類異常,我們必須捕獲進行處理。
Java 語言將派生於 RuntimeException 類或 Error 類的所有異常稱為“未檢查異常”。
5. 異常的處理辦法之一:捕獲異常
5.1 try
try 語句指定了一段程式碼,該段程式碼就是一次捕獲並處理的範圍。在執行過程中,當任意一條語句產生異常時,就會跳過該段中後面的程式碼。程式碼中可能會產生並丟擲一種或幾種型別的異常物件,它後面的catch語句要分別對這些異常做相應的處理
一個 try 語句必須帶有至少一個 catch 語句塊或一個 finally 語句塊 。
注:當異常處理的程式碼執行結束以後,是不會回到try語句去執行尚未執行的程式碼。
5.2 catch
1、每個 try 語句塊可以伴隨一個或多個 catch 語句,用於處理可能產生的不同型別的異常物件。
2.、常用方法:
toString ( )方法,顯示異常的類名和產生異常的原因
getMessage( ) 方法,只顯示產生異常的原因,但不顯示類名。
printStackTrace( ) 方法,用來跟蹤異常事件發生時堆疊的內容。
這些方法均繼承自 Throwable 類
3、catch 捕獲異常時的捕獲順序:
如果異常類之間有繼承關係,在順序安排上需注意。越是頂層的類(父類),越放在下面。再不然就直接把多餘的catch 省略掉。
5.3 finally
有些語句,不管是否發生了異常,都必須要執行,那麼就可以把這樣的語句放到finally 語句塊中。
通常在 finally 中關閉程式塊已開啟的資源,比如:檔案流、釋放資料庫連線等。
5.4 典型程式碼
public class TestException {
public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader("d:/a.txt");
char temp = (char) reader.read();
System.out.println("讀出的內容:" + temp);
} catch (FileNotFoundException e) {
System.out.println("檔案沒有找到!!");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
System.out.println("檔案讀取錯誤!");
} finally {
System.out.println(" 不管有沒有異常,我肯定會被執行!");
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.5 try, catch,finally ,return 執行順序
執行順序:
1.執行 try,catch , 給返回值賦值
2.執行finally
3.return
6. 異常的處理辦法之二:宣告異常(throws 子句)
當 Checked Exception 產生時,不一定立刻處理它,可以再把異常 Throws 出去。
在方法中使用 try-chatch-finally 由這個方法處理異常。在一些情況下,當前方法並不需要處理髮生的異常,而是向上傳遞給呼叫它的方法處理。
如果一個方法中可能產生某種異常,但是並不能確定如何處理這種異常,則應根據異常規範在方法的首部宣告該方法可能丟擲的異常。
如果一個方法丟擲多個已檢查異常,就必須在方法的首部列出所有的異常,之間以逗號隔開。
6.1 典型程式碼
public class FileTest {
public static void main(String[] args) {
try {
readFile("d:/a.txt");
} catch (FileNotFoundException e) {
System.out.println("所需要的檔案不存在!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("檔案讀寫出錯誤!");
e.printStackTrace();
}
}
public static void readFile(String fileName) throws IOException {
FileReader in = new FileReader(fileName);
try {
int tem = 0;
tem = in.read();
while (tem != -1) {
System.out.println((char) tem);
tem = in.read();
}
} finally {
in.close();
}
}
}
6.2 方法重寫中宣告異常原則
子類宣告的異常範圍不能超過父類宣告的範圍。包含如下意思:
- 父類沒有宣告異常,子類也不能;
- 不可丟擲原有方法丟擲異常類的父類或上層類
- 丟擲的異常型別的數目不可以比原有的方法丟擲的還多(不是指個數)
7. 異常的處理辦法之三:手動丟擲異常(throw子句)
Java 異常類物件除在程式執行過程中出現異常時由系統自動生成並丟擲,也可根據需要手工建立並丟擲。
在捕獲一個異常前,必須有一段程式碼先生成異常物件並把它丟擲。這個過程我們可以手工做,也可以由 JRE 來實現,但是他們呼叫的都是 throw 子句。
對於一個已經存在的異常類,丟擲該類異常物件過程如下:
找到一個合適的異常類。
建立一個該類的物件。
將物件丟擲
File f=new File("c:/tt.txt");
if(!f.exists()){
try{
throw new FileNotFoundException("File can't be found!");
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
8. 自定義異常
在程式中,可能會遇到任何標準異常類都沒有充分的描述清楚的問題,這種情況下可以建立自己的異常類。
怎麼做:
從 Exception 類或者它的子類派生一個子類即可
習慣上,定義的類應該包含 2 個構造器:一個是預設的構造器,另一個是帶有詳細資訊的構造器。
典型程式碼
class IllegalAgeException extends Exception {
public IllegalAgeException() {
}
public IllegalAgeException(String msg) {
super(msg);
}
}
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if (age < 0) throw new IllegalAgeException("人的年齡不應該為負數");
this.age = age;
}
public String toString() {
return "name is " + name + " and age is " + age;
}
}
public class MyExceptionTest {
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);
}
}
9. 使用異常機制建議
- 要避免使用異常處理代替錯誤處理,這樣會降低程式的清晰性,並且效率低下( Java 是採用面向物件的方式來處理異常的,所以也是會有一定的開銷)
- 只在異常情況下使用異常機制
- 不要進行小粒度的異常處理---應該將整個任務儘可能包裝在一個 Try 語句塊中
- 異常往往在低層丟擲,高層處理(捕獲)
10. 總結
- 一個圖
- 五個關鍵字(try, catch, finally, throws, throw)
- 先逮小的(子類),再逮大的(父類)
- 異常和重寫的關係
- 自定義異常
現在的喜歡,其實不是真正的喜歡,只是因為不瞭解而已,真正的喜歡,是建立在非常瞭解的基礎上。瞭解 java 基礎,喜歡上程式設計,不再迷茫。
喜歡文章的話可以掃描關注微信公眾號
搜尋微信公眾號:Java知其所以然,可免費領取某課、Java 後端面經等資源,還有統一環境(教你怎麼配置一套開發環境)視訊領取