??前面的話??
本篇文章帶大家認識Java基礎知識——例外,在理想的情況下,用戶輸入的資料格式永遠是正確的,一個程式也許可能永遠沒有bug,但是在實際情況當中,這種理想的情況非常少,用戶輸入資料格式出現錯誤是非常常見的,需要一種機制解決類似的問題,而在Java中引入了例外的概念,本篇博客將圍繞什么是例外,例外怎么解決兩個維度對例外做一個詳細地分析,
📒博客主頁:未見花聞的博客主頁
🎉歡迎關注🔎點贊👍收藏??留言📝
📌本文由未見花聞原創,CSDN首發!
📆首發時間:🌴2021年12月8日🌴
??堅持和努力一定能換來詩與遠方!
💭參考書籍:📚《Java核心技術》,📚《Java編程思想》,📚《Effective Java》
💬參考在線編程網站:🌐牛客網🌐力扣
博主的碼云gitee,平常博主寫的程式代碼都在里面,
博主的github,平常博主寫的程式代碼都在里面,
🙏作者水平很有限,如果發現錯誤,一定要及時告知作者哦!感謝感謝!
📌導航小助手📌
- 1.初見Java例外
- 1.1熟悉的面孔
- 1.2防御式編程
- 1.3例外的優點
- 2.例外處理
- 2.1捕獲例外
- 2.1.1捕獲一個例外
- 2.1.2捕獲多種例外
- 2.2finally
- 2.3Java例外體系
- 2.4例外處理的注意事項
- 2.4.1例外捕捉時的注意事項
- 2.4.2例外處理的流程
- 2.5手動拋出例外
- 3.自定義例外

1.初見Java例外
1.1熟悉的面孔
在Java的學習程序中,一定遇到過除數為0,陣列越界,對空參考操作等,這些情況可以通過編譯,但是在運行的時候編譯器會報出例外,這些例外就是Java例外機制中的一部分,
System.out.println(10 / 0);//除數為0

int[] arr = new int[10];
System.out.println(arr[11]);

String str = null;
System.out.println(str.length());

A
r
i
t
h
m
e
t
i
c
E
x
c
e
p
t
i
o
n
,
A
r
r
a
y
I
n
d
e
x
O
u
t
O
f
B
o
u
n
d
s
E
x
c
e
p
t
i
o
n
,
N
u
l
l
P
o
i
n
t
e
r
E
x
c
e
p
t
i
o
n
ArithmeticException,ArrayIndexOutOfBoundsException,NullPointerException
ArithmeticException,ArrayIndexOutOfBoundsException,NullPointerException都是初學Java時常遇到的一部分例外,但是Java例外機制包括的不僅僅只有例外,還有錯誤也是屬于Java例外機制范圍內的,

1.2防御式編程
錯誤在代碼中是客觀存在的. 因此我們要讓程式出現問題的時候及時通知程式猿, 我們有兩種主要的方式:
LBYL: Look Before You Leap. 在操作之前就做充分的檢查.
EAFP: It’s Easier to Ask Forgiveness than Permission. “事后獲取原諒比事前獲取許可更容易”. 也就是先操作, 遇到問題再處理.
打個比方,有一天你心血來潮,看到一位非常漂亮的小姐姐,你很想牽她的手,但是你非常的有禮貌,先詢問這位小姐姐:我能牽你的手嗎?,這位小姐姐毫不猶豫地說:不行!,這一種情形就是LBYL方式;與此同時,另一位比較強悍的兄弟也想牽這位小姐姐的手,于是直接拿起這位小姐姐的手,牽了起來,然后這位小兄弟被扇了一巴掌,這種情形就是EAFP方式,
Java例外核心思想就是EAFP,
1.3例外的優點
使用例外能夠更好地處理業務,代碼也更有條理,更加清晰明白,不使用例外代碼正常的代碼與可能例外的代碼混在一起,比較混亂,
比如一個模擬實作一個游戲開始的程序:
不使用例外:
public class Test2 {
public static void main(String[] args) {
boolean ret = false;//賬號密碼匹配結果
ret = 密碼驗證結果();
if (!ret) {
//處理游戲登錄失敗業務
}
ret = 加載游戲結果();
if (!ret) {
//處理加載游戲失敗業務
}
ret = 聯機匹配結果();
if (!ret) {
//處理聯機匹配失敗業務
}
ret = 氪金是否成功結果();
if (!ret) {
//處理氪金失敗業務
}
}
}
使用例外:
public class Test2 {
public static void main(String[] args) {
try {
密碼驗證();
加載游戲();
聯機匹配();
氪金();
}catch (密碼驗證例外) {
//處理密碼錯誤例外
}catch (加載游戲例外) {
//處理加載游戲例外
}catch (聯機匹配例外) {
//處理聯機匹配例外
}catch (氪金例外) {
//處理氪金例外
}
}
}
2.例外處理
2.1捕獲例外
想要捕獲一個例外,前提需要知道可能會發生什么型別的例外,最常見的例外就是前面所舉例的
A
r
i
t
h
m
e
t
i
c
E
x
c
e
p
t
i
o
n
,
A
r
r
a
y
I
n
d
e
x
O
u
t
O
f
B
o
u
n
d
s
E
x
c
e
p
t
i
o
n
,
N
u
l
l
P
o
i
n
t
e
r
E
x
c
e
p
t
i
o
n
ArithmeticException,ArrayIndexOutOfBoundsException,NullPointerException
ArithmeticException,ArrayIndexOutOfBoundsException,NullPointerException分別為除數為0例外,陣列越界例外,空指標例外,
當然,Java例外體系中的例外種類非常多,Exception類下的直接子類就有:

RuntimeException類是目前我們接觸最多的,前面列舉的幾個常見的例外都是該類的子類,

2.1.1捕獲一個例外
我們在進行除法計算時,可能會出現除數為0,訪問陣列物件時,可能出現陣列越界,使用一個物件時,可能會出現空指標例外,知道一段代碼可能出現某種或某些例外,我們可以對例外進行捕獲,語法規則為:
try {
//可能出現例外的代碼段
}catch (例外類 例外變數名) {
//處理該例外
}
try 代碼塊中放的是可能出現例外的代碼,
catch代碼塊中放的是出現例外后的處理行為,
public class Test3 {
public static void main(String[] args) {
int a = 0;
try {
System.out.println(8 / a);
}catch (ArithmeticException e) {
System.out.println("捕捉到了除數為0例外!");
}
int[] arr = new int[10];
try {
System.out.println(arr[20]);
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕捉到了一個陣列越界例外!");
}
String str = null;
try {
System.out.println(str.equals("aaa"));
}catch (NullPointerException e) {
System.out.println("捕捉到了一個空指標例外!");
}
}
}

可以呼叫例外類中的
p
r
i
n
t
S
t
a
c
k
T
r
a
c
e
(
)
方
法
printStackTrace()方法
printStackTrace()方法列印例外錯誤資訊:
public class Test3 {
public static void main(String[] args) {
int a = 0;
try {
System.out.println(8 / a);
}catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了除數為0例外!");
}
int[] arr = new int[10];
try {
System.out.println(arr[20]);
}catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("捕捉到了一個陣列越界例外!");
}
String str = null;
try {
System.out.println(str.equals("aaa"));
}catch (NullPointerException e) {
e.printStackTrace();
System.out.println("捕捉到了一個空指標例外!");
}
}
}

如果例外沒有被主動捕捉(使用try…catch陳述句捕捉),這個例外會交給JVM處理,一旦交給JVM處理,發生例外,將非正常終止程式,后續代碼不會執行,
public class Test6 {
public static void main(String[] args) {
int[] arr = new int[10];
System.out.println(arr[20]);//發生陣列越界例外,該例外沒有被捕獲,將交給JVM處理
System.out.println("我是例外代碼的后續代碼,我被執行了!");
}
}

代碼非正常退出,例外處后的代碼不會被執行!
主動捕獲例外,例外處后面的代碼會執行,并且程式正常退出:
public class Test5 {
public static void main(String[] args) {
int[] arr = new int[10];
try {
System.out.println(arr[20]);
}catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("捕捉到了一個陣列越界例外!");
}
System.out.println("我是例外代碼的后續代碼,我被執行了!");
}
}

2.1.2捕獲多種例外
Java例外機制支持多種例外的捕獲,最常用的方法,就是使用多個catch陳述句,捕捉多個例外的具體型別,
一個try…catch陳述句,catch陳述句可以存在多個:
public class Test4 {
public static void main(String[] args) {
try {
//可能出現例外的代碼段
}catch (例外類 例外變數名) {
//處理該例外
}catch (...) {
//處理
}...
}
}
而對于try陳述句中的代碼段,當代碼段中遇到例外時,發生例外的后面的所有代碼段將不再執行,會直接執行對應catch中的陳述句,
比如,我將三句都有例外的代碼寫在同一個try陳述句中,使用多個catch陳述句捕捉這三種型別的例外,實際上只會捕捉到第一個例外,
public class Test7 {
public static void main(String[] args) {
int a = 0;
int[] arr = new int[10];
String str = null;
try {
System.out.println(8 / a);
System.out.println(arr[20]);
System.out.println(str.equals("aaa"));
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了除數為0例外!");
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("捕捉到了一個陣列越界例外!");
} catch (NullPointerException e) {
e.printStackTrace();
System.out.println("捕捉到了一個空指標例外!");
}
}
}

try陳述句中第一個例外陳述句為除數為0而引發的算術例外,所以最終捕捉的例外只有算術例外,
如果第一個陳述句沒有例外,則捕捉的是后面第一個發生例外陳述句的例外,也就是說一個try陳述句只能捕獲一個例外,
public class Test7 {
public static void main(String[] args) {
int a = 2;
int[] arr = new int[10];
String str = null;
try {
System.out.println(8 / a);
System.out.println(arr[20]);
System.out.println(str.equals("aaa"));
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了除數為0例外!");
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("捕捉到了一個陣列越界例外!");
} catch (NullPointerException e) {
e.printStackTrace();
System.out.println("捕捉到了一個空指標例外!");
}
}
}

除了使用多個catch陳述句,還可以使用|分隔符在一個catch陳述句中定義多種型別的例外類:
public class Test8 {
public static void main(String[] args) {
int[] arr = new int[10];
String str = null;
try {
System.out.println(2/0);
} catch (ArrayIndexOutOfBoundsException | NullPointerException | ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了一個除數為0例外或陣列越界例外或空指標例外!");
}
}
}

public class Test8 {
public static void main(String[] args) {
int[] arr = new int[10];
String str = null;
try {
System.out.println(arr[20]);
System.out.println(str.equals("aaa"));
} catch (ArrayIndexOutOfBoundsException | NullPointerException | ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了一個除數為0例外或陣列越界例外或空指標例外!");
}
}
}

public class Test8 {
public static void main(String[] args) {
int[] arr = new int[10];
String str = null;
try {
System.out.println(str.equals("aaa"));
} catch (ArrayIndexOutOfBoundsException | NullPointerException | ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了一個除數為0例外或陣列越界例外或空指標例外!");
}
}
}

也可以直接使用這些類的父類RuntimeExcept或父類的父類Exception處理其所有子型別別的例外,但是不推薦,因為范圍太廣了,如果沒有
p
r
i
n
t
S
t
a
c
k
T
r
a
c
e
(
)
方
法
printStackTrace()方法
printStackTrace()方法,根本不知道程式發生了什么例外,
public class Test8 {
public static void main(String[] args) {
int[] arr = new int[10];
String str = null;
try {
System.out.println(str.equals("aaa"));
} catch (RuntimeException e) {
e.printStackTrace();
System.out.println("捕捉到了一個運行時例外!");
}
}
}

最后,還有一點需要注意,就是使用多個catch陳述句時,子類例外要在父類例外前面,否則編譯器會報錯!這一點其實很好理解,正所謂“小的打不過,老的再來!”,

我把NullPointerException定義在其父類RuntimeException后面,編譯器報錯了,我換一下編譯器就沒有報錯了,程式也能夠正常運行,


所以,catch陳述句捕獲例外時,捕獲例外的順序應該是:
子
類
?
>
父
類
子類 -> 父類
子類?>父類
2.2finally
在try...catch陳述句的基礎之上,后面還可以加上finally陳述句,無論是否發生例外,例外是否被捕獲,該陳述句里面的代碼塊都會被執行,一般用于資源的關閉,比如常見的Scanner類,使用該類進行輸入是需要進行資源關閉的,語法規則:
try {
//可能出現例外的代碼段
}catch (例外類 例外變數名) {
//處理該例外
}catch (...) {
//處理
}finally {
//處理,不論例外是否捕獲,例外是否交給JVM處理,該陳述句都會執行
}
finally 代碼塊中的代碼用于處理善后作業, 會在最后執行,
import java.util.InputMismatchException;
import java.util.Scanner;
public class Test10 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try {
int a = sc.nextInt();
System.out.println(10 / a);
} catch (InputMismatchException e) {
e.printStackTrace();
System.out.println("輸入資料型別錯誤!");
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("算術例外,除數為0!");
}
sc.close();//關閉資源
}
}

因為sc.close()陳述句不論什么情況都要執行,所以該代碼可以改成:
import java.util.InputMismatchException;
import java.util.Scanner;
public class Test11 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try {
int a = sc.nextInt();
System.out.println(10 / a);
} catch (InputMismatchException e) {
e.printStackTrace();
System.out.println("輸入資料型別錯誤!");
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("算術例外,除數為0!");
} finally {
sc.close();//關閉資源
}
}
}

剛才的代碼可以有一種等價寫法, 將 Scanner 物件在 try 的 ( ) 中創建, 就能保證在 try 執行完畢后自動呼叫 Scanner的 close 方法,上面的代碼也等價于:
import java.util.InputMismatchException;
import java.util.Scanner;
public class Test11 {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
int a = sc.nextInt();
System.out.println(10 / a);
} catch (InputMismatchException e) {
e.printStackTrace();
System.out.println("輸入資料型別錯誤!");
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("算術例外,除數為0!");
}
}
}
使用finally的弊端:
public class Test12 {
public static int func() {
try {
return 10;
}finally {
return 20;
}
}
public static void main(String[] args) {
System.out.println(func());
}
}

所以使用finally時,要避免在finally陳述句中進行回傳值回傳,
2.3Java例外體系
Java 內置了豐富的例外體系, 用來表示不同情況下的例外,

- 頂層類
Throwable派生出兩個重要的子類,Error和Exception, -
Error指的是 Java 運行時內部錯誤和資源耗盡錯誤. 應用程式不拋出此類例外. 這種內部錯誤一旦出現,除了告知用戶并使程式終止之外, 再無能無力, 如遞回時造成的堆疊溢位,堆溢位, -
Exception是我們程式猿所使用的例外類的父類,它的直接子類有很多,圖中只展示了3種, -
Exception有一個子類稱為RuntimeException, 這里面又派生出很多我們常見的例外類NullPointerException,IndexOutOfBoundsException等, -
Error類與RuntimeException類,為非受查例外,其他例外類為受查例外,
受查例外必須使用try...catch陳述句進行處理,否則報錯,非受查例外可以使用try...catch陳述句,也可以不使用,
例外的種類有很多, 我們要根據不同的業務場景來決定,
對于比較嚴重的問題(例如和算錢相關的場景), 應該讓程式直接崩潰, 防止造成更嚴重的后果,
對于不太嚴重的問題(大多數場景), 可以記錄錯誤日志, 并通過監控報警程式及時通知程式猿,
對于可能會恢復的問題(和網路相關的場景), 可以嘗試進行重試,
在我們當前的代碼中采取的是經過簡化的第二種方式. 我們記錄的錯誤日志是出現例外的方法呼叫資訊, 能很快速的讓我們找到出現例外的位置. 以后在實際作業中我們會采取更完備的方式來記錄例外資訊,
2.4例外處理的注意事項
2.4.1例外捕捉時的注意事項
- 程式發生例外未被
try捕獲處理,將會交給JVM處理,一旦交給JVM處理,程式直接非正常終止, -
catch陳述句捕獲例外的順序,從子類到父類,但是最好使用具體例外類處理, - 一旦
try中出現例外, 那么try代碼塊中的程式就不會繼續執行, 而是交給catch中的代碼來執行,catch執行完畢會繼續往下執行程式后續代碼, -
finally里面的代碼,無論例外是否被捕獲,都會執行, - 由于
Exception類是所有例外類的父類, 因此可以用這個型別表示捕捉所有型別的例外, -
try陳述句需要搭配catch或finally使用,
2.4.2例外處理的流程
- 程式先執行 try 中的代碼,
- 如果 try 中的代碼出現例外, 就會結束 try 中的代碼, 看和 catch 中的例外型別是否匹配,
- 如果找到匹配的例外型別, 就會執行 catch 中的代碼,
- 如果沒有找到匹配的例外型別, 就會將例外向上傳遞到上層呼叫者,
- 無論是否找到匹配的例外型別, finally 中的代碼都會被執行到(在該方法結束之前執行),
- 如果上層呼叫者也沒有處理的了例外, 就繼續向上傳遞,一直到 main 方法也沒有合適的代碼處理例外, 就會交給 JVM 來進行處理, 此時程式就會例外終止,
2.5手動拋出例外
除了 Java 內置的類會拋出一些例外之外, 程式猿也可以手動拋出某個例外,使用 throw 關鍵字完成這個操作,
public class Test13 {
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除0例外!");
}
return a / b;
}
public static void main(String[] args) {
System.out.println(divide(12, 0));
}
}

我們在處理例外的時候, 通常希望知道這段代碼中究竟會出現哪些可能的例外,我們可以使用 throws 關鍵字, 把可能拋出的例外顯式的標注在方法定義的位置, 從而提醒呼叫者要注意捕獲這些例外,相當于宣告效果,
public class Test14 {
public static int divide(int a, int b) throws ArithmeticException{
if (b == 0) {
throw new ArithmeticException("除0例外!");
}
return a / b;
}
public static void main(String[] args) {
try {
System.out.println(divide(12, 0));
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
}

3.自定義例外
Java內置了很多的例外,但是總會有需求在內置的例外中找不到合適的,這個時候就需要自己定義一個合適的例外,來應對需求,
我們以一個用戶登錄來介紹自定義例外的使用,在此之前,需要知道:
自定義的例外需要繼承Java內置的例外類,通常會繼承自 Exception 或者 RuntimeException,繼承Exception表示自定義的例外時受查例外,繼承 RuntimeException表示自定義的例外是非受查例外,
用戶進行輸入賬號密碼登錄時,可能會出現賬號不存在或者密碼錯誤的情況,這兩種情況是例外情況,需要進行處理,所以可以自定義兩個類NameException和PasswordException,前者表示賬號不存在例外,后者表示密碼錯誤例外,由于該例外是在用戶輸入時才會出現,所以不妨繼承RuntimeException,定義為非受查例外,
class NameException extends RuntimeException {
public NameException(String exceptionMessage) {
super(exceptionMessage);
}
}
class PasswordException extends RuntimeException {
public PasswordException(String exceptionMessage) {
super(exceptionMessage);
}
}
模擬用戶登錄:
public class Test15 {
private static String userName = "未見花聞";
private static String password = "5201314";
public static void login(String userName, String password) throws NameException, PasswordException{
if (!Test15.userName.equals(userName)) {
throw new NameException("用戶名不存在!");
}
if (!Test15.password.equals(password)) {
throw new PasswordException("密碼錯誤!");
}
System.out.println("登陸成功");
}
public static void main(String[] args) {
try {
login("csdn", "123456");
}catch (NameException | PasswordException e) {
e.printStackTrace();
}
try {
login("未見花聞", "123456");
}catch (NameException | PasswordException e) {
e.printStackTrace();
}
System.out.println("============");
try {
login("未見花聞", "5201314");
}catch (NameException | PasswordException e) {
e.printStackTrace();
}
}
}


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/377060.html
標籤:java
