Java 基礎(chǔ)語(yǔ)法 異常處理
前些章節(jié)的知識(shí)點(diǎn)有時(shí)會(huì)涉及到異常的知識(shí),如果沒(méi)有專(zhuān)門(mén)學(xué)習(xí)過(guò)異常的小伙伴可能看的有點(diǎn)疑惑。今天這節(jié)就是為了講解異常,讓我們來(lái)了解什么是異常,它的作用是啥,怎么使用異常。
1. 異常的背景
1.1 邂逅異常
大家在學(xué)習(xí) Java 時(shí),應(yīng)該也遇見(jiàn)過(guò)一些異常了,例如
算術(shù)異常:
System.out.println(10 / 0);
結(jié)果為:Exception in thread "main" java.lang.ArithmeticException: / by zero
數(shù)組越界異常:
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[100]);
結(jié)果為:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
空指針異常:
int[] arr = null; System.out.println(arr.length);
結(jié)果為:Exception in thread "main" java.lang.NullPointerException
那么什么是異常呢?
異常是程序中的一些錯(cuò)誤,但并不是所有的錯(cuò)誤都是異常,并且錯(cuò)誤有時(shí)候是可以避免的。
1.2 異常和錯(cuò)誤
- 異常被分為下面兩種
運(yùn)行時(shí)異常(非受查異常):
在程序運(yùn)行(通過(guò)編譯已經(jīng)得到了字節(jié)碼文件,再由 JVM 執(zhí)行)的過(guò)程當(dāng)中發(fā)生的異常,是可能被大家避免的異常,這些異常在編譯時(shí)可以被忽略。
例如:算數(shù)異常、空指針異常、數(shù)組越界異常等等
編譯時(shí)異常(受查異常):
編譯時(shí)發(fā)生的異常,這個(gè)異常是大家難以預(yù)見(jiàn)的,這些異常在編譯時(shí)不能被簡(jiǎn)單的忽略。
例如:要打開(kāi)一個(gè)不存在的文件時(shí),一個(gè)異常就發(fā)生了
除了異常我們我們也要了解下錯(cuò)誤
錯(cuò)誤:
錯(cuò)誤不是異常,而是脫離程序員控制的問(wèn)題,錯(cuò)誤在代碼中通常被忽略。
例如:當(dāng)棧溢出時(shí),一個(gè)錯(cuò)誤就發(fā)生了,這是編譯檢查不到的
public static void func(){ func(); } public static void main(String[] args){ func(); }結(jié)果為:
Exception in thread "main" java.lang.StackOverflowError
那么異常和錯(cuò)誤的區(qū)別是什么呢?
出現(xiàn)錯(cuò)誤必須由我們程序員去處理它的邏輯錯(cuò)誤,而出現(xiàn)異常我們只要去處理異常就好了
如果有疑惑的伙伴通過(guò)后面的介紹你會(huì)逐漸了解它們的區(qū)別
1.3 Java 異常的體系(含體系圖)
Java 中異常的種類(lèi)是很多的,我將一些異常收集并歸類(lèi)如下

其中
Error是錯(cuò)誤,Exception是異常。而異常中又分為了兩種,黃色的是編譯時(shí)異常,橙色的是運(yùn)行時(shí)異常。
但是這張圖不僅僅說(shuō)明了上述的關(guān)系,我們還要知道
每個(gè)異常其實(shí)都是一個(gè)類(lèi),并且箭頭代表了繼承的關(guān)系
我們可以通過(guò)一個(gè)代碼來(lái)理解
int[] arr = null; System.out.println(arr.length); // 結(jié)果為:Exception in thread "main" java.lang.NullPointerException
此時(shí)我們點(diǎn)擊這個(gè)異常 NullPointerException 就轉(zhuǎn)到了它的定義,我們會(huì)看到

我們可以得到以下結(jié)論:
NullPointerException是一個(gè)類(lèi)- 這個(gè)類(lèi)繼承了
RuntimeException這個(gè)類(lèi)
為了刨根究底,我們繼續(xù)轉(zhuǎn)到 RuntimeException 這個(gè)類(lèi)看看

我們又得到了以下結(jié)論:
RuntimeException是一個(gè)類(lèi)- 這個(gè)類(lèi)繼承了
Exception這個(gè)類(lèi)
繼續(xù)刨根究底,我們又可以看到
誒,此時(shí)我們?cè)賹?duì)照著體系圖我們就可以理解清除這張圖的所有意思,并且此時(shí)對(duì)異常又有了個(gè)全面對(duì)認(rèn)識(shí)
而今天我們的主角是異常,即 Exception,接下來(lái)我將會(huì)對(duì)它進(jìn)行解析。
1.4 異常的核心思想
作為一個(gè)程序員,我們經(jīng)常都面對(duì)著

錯(cuò)誤在代碼中的存在我們不言而喻,因此就產(chǎn)生了兩種主要針對(duì)錯(cuò)誤的方式
方式一(LBYL):在操作之前就做充分的檢查
方式二(EAFP):直接操作,有錯(cuò)誤再解決
而異常的核心思想就是 EAFP
1.5 異常的好處
那么核心思想為 EAFP 的異常有什么好處呢?
我們可以隨便舉一個(gè)例子,比如你打一把王者,我們要進(jìn)行登錄、匹配、確認(rèn)游戲、選擇英雄等等的操作。
如果使用 LBYL 風(fēng)格的代碼,我們就要對(duì)每一步都做好充分的檢查之后,再進(jìn)行下一步,簡(jiǎn)單寫(xiě)個(gè)代碼如下
boolean ret = false;
ret = log();
if(!=ret){
// 處理登錄游戲錯(cuò)誤
return;
}
ret = matching();
if(!=ret){
// 處理匹配游戲錯(cuò)誤
return;
}
ret = confirm();
if(!=ret){
// 處理確認(rèn)游戲錯(cuò)誤
return;
}
ret = choose();
if(!=ret){
// 處理選擇游戲錯(cuò)誤
return;
}
而使用 EAFP 的風(fēng)格,代碼則是這樣的
try{
log();
matching();
confirm();
choose();
}catch(登錄游戲異常){
// 處理登錄游戲錯(cuò)誤
}catch(匹配游戲異常){
// 處理匹配游戲錯(cuò)誤
}catch(確認(rèn)游戲異常){
// 處理確認(rèn)游戲錯(cuò)誤
}catch(選擇游戲異常){
// 處理選擇游戲錯(cuò)誤
}
兩種方式的代碼一對(duì)比,大家也可以看得出哪一種更好。EAFP 風(fēng)格的就可以將流程和處理異常的代碼分開(kāi),看起來(lái)更加舒服。而這也就是使用異常的好處之一。上述代碼運(yùn)用了異常的基本用法,后續(xù)會(huì)介紹。
2. 異常的基本用法
2.1 捕獲異常
2.1.1 基本語(yǔ)法
try{
// 有可能出現(xiàn)異常的語(yǔ)句
}[catch(異常類(lèi)型 異常對(duì)象){
// 出現(xiàn)異常后的處理行為
}...]
[finally{
// 異常的出口
}]
try代碼塊中放的是可能出現(xiàn)異常的代碼catch代碼塊中放的是出現(xiàn)異常后的處理行為finally代碼塊中的代碼用于處理善后工作,會(huì)在最后執(zhí)行- 其中
catch和finally都可以根據(jù)情況選擇加或者不加
2.1.2 示例一
首先我們看一個(gè)不處理異常的代碼
int[] arr = {1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
結(jié)果是:

我們分析一下這個(gè)結(jié)果,首先它告訴我們?cè)?
main方法中出現(xiàn)了數(shù)組越界的異常,原因就是100這個(gè)數(shù)字。下面它又告訴我們了這個(gè)異常的具體位置。并且通過(guò)這個(gè)結(jié)果我們知道,當(dāng)代碼出現(xiàn)異常之后,程序就中止了,異常代碼后面的代碼就不會(huì)執(zhí)行了。
那么為什么這里拋出異常之后,后面的代碼就不再執(zhí)行了呢?
因?yàn)楫?dāng)沒(méi)有處理異常的時(shí)候,一旦程序發(fā)生異常,這個(gè)異常就會(huì)交給 JVM 來(lái)處理。
而一旦交給了 JVM 處理異常,程序就會(huì)立即終止執(zhí)行!
這也就是為什么我們會(huì)有自己處理異常這個(gè)行為
我們?nèi)绻由?try catch 自己處理異常
int[] arr = {1, 2, 3};
try {
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("數(shù)組越界!");
}
System.out.println("after try catch");
結(jié)果是:

我們發(fā)現(xiàn) try 中出現(xiàn)了異常的語(yǔ)句,并且我們針對(duì)這個(gè)異常做出了處理的行為。而 try catch 后面的程序依然可以繼續(xù)執(zhí)行
我們?cè)谏鲜龃a中處理異常時(shí) catch 里面用的語(yǔ)句就是直接告訴它出現(xiàn)了什么問(wèn)題,但是如果我們想要知道這是什么異常,在代碼的第幾行有問(wèn)題的話,就可以再加一個(gè)調(diào)用棧。
什么是調(diào)用棧呢?
方法之間存在相互調(diào)用關(guān)系,這種調(diào)用關(guān)系可以用“調(diào)用?!眮?lái)描述。在 JVM 中有一塊內(nèi)存空間稱(chēng)為“虛擬機(jī)?!?,這是專(zhuān)門(mén)存儲(chǔ)方法之間調(diào)用關(guān)系的。當(dāng)代碼中出現(xiàn)異常的時(shí)候,我們就可使用 e.printStackTrace(); 來(lái)查看出現(xiàn)異常代碼的調(diào)用棧
2.1.3 示例二(含使用調(diào)用棧)
int[] arr = {1, 2, 3};
try {
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("數(shù)組越界!");
e.printStackTrace();
}
System.out.println("after try catch");
結(jié)果是:

2.1.4 示例三(可以使用多個(gè) catch 捕獲不同的異常)
int[] arr = {1, 2, 3};
try {
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("數(shù)組越界!");
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("空指針異常");
e.printStackTrace();
}
System.out.println("after try catch");
這個(gè)代碼里面有多個(gè) catch,他會(huì)捕獲到第一個(gè)出現(xiàn)異常的位置
2.1.5 示例四(可以使用一個(gè) catch 捕獲所有異常,不推薦)
int[] arr = {1, 2, 3};
try {
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch(Exception e){
e.printStackTrace();
}
System.out.println("after try catch");
其中我們使用了
Exception這個(gè)類(lèi),我們知道它是所有異常的父類(lèi),因此可以用來(lái)捕獲所有異常。但是這個(gè)方法是不推薦的,因?yàn)楫惓L嗔?,我們不容易定位?wèn)題
并且我們能得到一個(gè)結(jié)論
catch進(jìn)行類(lèi)型匹配的時(shí)候,不光會(huì)匹配相同類(lèi)型的異常,也能捕獲目標(biāo)異常類(lèi)型的子類(lèi)對(duì)象
2.1.6 示例五(使用 finally,它之間的代碼將在 try 語(yǔ)句后執(zhí)行)
int[] arr = {1, 2, 3};
try {
arr = null;
System.out.println(arr.length);
}catch(NullPointerException e){
e.printStackTrace();
}finally{
System.out.println("finally 執(zhí)行啦!");
}
System.out.println("after try catch");
結(jié)果為:

我們緊接著再看一個(gè)代碼,我將異常給改正確
int[] arr = {1, 2, 3};
try {
System.out.println(arr.length);
}catch(NullPointerException e){
e.printStackTrace();
}finally{
System.out.println("finally 執(zhí)行啦!");
}
System.out.println("after try catch");
上述代碼就沒(méi)有錯(cuò)誤了,但是結(jié)果是

我們就得出了這個(gè)結(jié)論
無(wú)論
catch是否捕獲到異常,都要執(zhí)行finally語(yǔ)句
finally 是用來(lái)處理善后工作的,例如釋放資源是可以被做到的。如果大家對(duì)于使用 finally 釋放資源有疑惑,可以先看示例八,因?yàn)樵?finally 中加入 Scanner 的 close 方法就是釋放資源的一種例子
2.1.7 示例六(finally 引申的思考題)
public static int func(){
try{
return 10;
}catch(NullPointerException e){
e.printStackTrace();
}finally{
return 1;
}
}
public static void main(String[] args) {
int num = func();
System.out.println(num);
}
結(jié)果為:1
因?yàn)?
finally塊永遠(yuǎn)是最后執(zhí)行的。并且你也無(wú)法在這個(gè)代碼之后執(zhí)行其他語(yǔ)句,因?yàn)椴还苡袥](méi)有捕獲到異常都要執(zhí)行finally中的return語(yǔ)句去終止代碼
2.1.8 示例七(使用 try 負(fù)責(zé)回收資源)
在演示代碼前要先補(bǔ)充一個(gè)關(guān)于 Scanner 的知識(shí)
我們知道使用
Scanner類(lèi)可以幫助我們進(jìn)行控制臺(tái)輸入語(yǔ)句,但是Scanner還是一種資源,而資源使用完之后是需要回收的,就像是我們打開(kāi)了一瓶水喝了點(diǎn)還要蓋上它。故用完后我們可以加上close方法來(lái)進(jìn)行回收,如Scanner reader = new Scanner(System.in); int a = reader.nextInt(); reader.close();
而 try 有一種寫(xiě)法可以在它執(zhí)行完畢后自動(dòng)調(diào)用 Scanner 的 close 方法
try(Scanner sc = new Scanner(System.in){
int num = sc.nextInt();
}catch(InputMismatchException e){
e.printStackTrace();
}
而這種方式的代碼風(fēng)格要比使用 finally 中含有 close 方法要好些
2.1.9 示例八(本方法中沒(méi)有合適的處理異常方式,就會(huì)沿著調(diào)用棧向上傳遞)
public static void func(){
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}
public static void main(String[] args){
try{
func();
}catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
}
結(jié)果為:

由于我們寫(xiě) func 方法時(shí)出現(xiàn)了異常沒(méi)有及時(shí)處理,但我們?cè)?main 方法中調(diào)用它了,所以就經(jīng)過(guò)方法之間互相的調(diào)用關(guān)系,我們一直到了 main 方法被調(diào)用的位置,并且此時(shí)有合適的處理異常的方法
若最終沒(méi)有找到合適的異常處理方法,最終該異常就會(huì)交給 JVM 處理,即程序就會(huì)終止
2.1.10 異常處理流程總結(jié)
- 程序先執(zhí)行
try中的代碼 - 如果 try 中的代碼出現(xiàn)異常,就會(huì)結(jié)束 try 中異常之后的代碼,并查看該異常和
catch中的異常類(lèi)型是否匹配 - 如果匹配,就會(huì)執(zhí)行
catch中的代碼 - 如果沒(méi)有匹配的,就會(huì)將異常向上傳遞到上層調(diào)用者
- 無(wú)論是否找到匹配類(lèi)型,
finally中的代碼都會(huì)被執(zhí)行 - 如果上層調(diào)用者沒(méi)有處理異常的方法,就會(huì)繼續(xù)向上傳遞
- 一直到 main 方法也沒(méi)有合適的代碼處理異常,就會(huì)交給
JVM來(lái)處理,此時(shí)程序就會(huì)終止
2.2 拋出異常
以上我們介紹的都是 Java 內(nèi)置的類(lèi)拋出的一些異常,除此之外我們也可以使用關(guān)鍵字 throw 手動(dòng)拋出一個(gè)異常,如
public static int divide(int x, int y) {
if (y == 0) {
throw new ArithmeticException("拋出除 0 異常");
}
}
public static void main(String[] args) {
System.out.println(divide(10, 0));
}
該代碼就是我們手動(dòng)拋出的異常,并且手動(dòng)拋出的異常還可以使用自定義的異常,后面將會(huì)介紹到
2.3 異常說(shuō)明
我們?cè)谔幚懋惓r(shí),如果有一個(gè)方法,里面很長(zhǎng)一大段,我們其實(shí)是希望很簡(jiǎn)單的就知道這段代碼有可能會(huì)出現(xiàn)哪些異常。故我們可以使用關(guān)鍵字 throws,把可能拋出的異常顯示的標(biāo)注在方法定義的位置,從而提醒使用者要注意捕獲這些異常,如
public static int divide(int x, int y) throws ArithmeticException{
if (y == 0) {
throw new ArithmeticException("拋出除 0 異常");
}
}
注意:
如果我們將
main方法拋出一個(gè)異常說(shuō)明,而main方法的調(diào)用者是JVM,所以如果在main 函數(shù)上拋出異常的話,就相當(dāng)于JVM來(lái)處理這個(gè)異常了
3. 自定義異常類(lèi)
Java 中雖然有豐富的異常類(lèi),但是實(shí)際上肯定還要一些情況需要我們對(duì)這些異常進(jìn)行擴(kuò)展,創(chuàng)建新的符合情景的異常。
那怎么創(chuàng)建自定義異常呢?首先我們就可以去看看原有的那些異常是怎么做的


兩異常
我們發(fā)現(xiàn)這兩個(gè)異常都是繼承在
RuntimeException這個(gè)類(lèi)的,并且都構(gòu)造了兩個(gè)構(gòu)造方法,分別是不帶參數(shù)和帶參數(shù)
而我模擬了一個(gè)登錄賬號(hào)的代碼
public class TestDemo {
private static String userName = "root";
private static String password = "123456";
public static void main(String[] args) {
login("admin", "123456");
}
public static void login(String userName, String password) {
if (!TestDemo.userName.equals(userName)) {
// 處理用戶名錯(cuò)誤
}
if (!TestDemo.password.equals(password)) {
// 處理密碼錯(cuò)誤
}
System.out.println("登陸成功");
}
}
通過(guò)這個(gè)模擬的場(chǎng)景,我們可以針對(duì)運(yùn)行時(shí)賬號(hào)和密碼是否正確寫(xiě)一個(gè)異常
class UserException extends RuntimeException{
public UserException(){
super();
}
public UserException(String s){
super(s);
}
}
class PasswordException extends RuntimeException{
public PasswordException(){
super();
}
public PasswordException(String s){
super(s);
}
}
緊接著我們?cè)偈謩?dòng)拋出異常
public class TestDemo {
private static String userName = "root";
private static String password = "123456";
public static void main(String[] args) {
login("admin", "123456");
}
public static void login(String userName, String password) {
if (!TestDemo.userName.equals(userName)) {
throws new UserException("用戶名錯(cuò)誤");
}
if (!TestDemo.password.equals(password)) {
throws new PasswordException("密碼錯(cuò)誤");
}
System.out.println("登陸成功");
}
}
所以我們創(chuàng)建新的異常時(shí),就是先思考這是哪種類(lèi)型的異常,再照貓畫(huà)虎。但是可能有疑惑,如果我們新建異常時(shí)統(tǒng)一繼承 Exception 不就行嗎?
No!由于
Exception分為編譯時(shí)異常和運(yùn)行時(shí)異常,使用Exception的話默認(rèn)是編譯時(shí)異常(即受查異常),而一段代碼可能拋出受查異常則必須顯示進(jìn)行處理。
故如果我們將上述新建的異常繼承 Exception 的話,就要再對(duì)代碼中的異常進(jìn)行處理,否則會(huì)直接報(bào)錯(cuò)
到此這篇關(guān)于Java 基礎(chǔ)語(yǔ)法 異常處理的文章就介紹到這了,更多相關(guān)Java 異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置方法,看這篇夠了
關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置問(wèn)題,很多朋友都搞不太明白,網(wǎng)上一搜配置方法多種哪種最精簡(jiǎn)呢,今天小編給大家分享一篇文章幫助大家快速掌握J(rèn)DK Tomcat eclipse MyEclipse配置技巧,需要的朋友參考下吧2021-06-06
Springboot 全局日期格式化處理的實(shí)現(xiàn)
這篇文章主要介紹了Springboot 全局日期格式化處理的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
在java中判斷兩個(gè)浮點(diǎn)型(float)數(shù)據(jù)是否相等的案例
這篇文章主要介紹了在java中判斷兩個(gè)浮點(diǎn)型(float)數(shù)據(jù)是否相等的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
JVM執(zhí)行引擎和垃圾回收要點(diǎn)總結(jié)
不論是在問(wèn)題現(xiàn)場(chǎng)還是跳槽面試,我們面對(duì)JVM性能問(wèn)題,依舊會(huì)束手無(wú)辭,它需要你對(duì)Java虛擬機(jī)的實(shí)現(xiàn)和優(yōu)化,有極為深刻的理解。所以我在這里整理了一下 JVM的知識(shí)點(diǎn)。今天說(shuō)說(shuō)虛擬機(jī)執(zhí)行引擎和垃圾回收,都是十足的干貨,請(qǐng)各位看官耐心批閱!2021-06-06
Springboot控制反轉(zhuǎn)與Bean對(duì)象的方法
文章介紹了Spring Boot中的控制反轉(zhuǎn)(IoC)概念,描述了IoC容器如何管理Bean的生命周期和依賴(lài)關(guān)系,它詳細(xì)講解了Bean的注冊(cè)過(guò)程,包括通過(guò)@ComponentScan和@Bean注解的方式,并討論了Bean的依賴(lài)注入方法,如構(gòu)造器注入、Setter注入和字段注入,感興趣的朋友一起看看吧2025-03-03
Springboot項(xiàng)目因?yàn)閗ackson版本問(wèn)題啟動(dòng)報(bào)錯(cuò)解決方案
這篇文章主要介紹了Springboot項(xiàng)目因?yàn)閗ackson版本問(wèn)題啟動(dòng)報(bào)錯(cuò)解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Spring3 MVC請(qǐng)求參數(shù)獲取的幾種方法小結(jié)
本篇文章主要介紹了Spring3 MVC請(qǐng)求參數(shù)獲取的幾種方法小結(jié),非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-03-03

