- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- 一文給你通俗易懂的講解Java異常
最簡(jiǎn)單的,看一個(gè)代碼示例:
public static void main(String[] args) { int a = 1; int b = 0; System.out.println(a / b); }
這段代碼有什么問(wèn)題?簡(jiǎn)單,除數不能為0對吧,我們打印輸出:
顯而易見(jiàn),程序出問(wèn)題了,不能正常執行了,這里出現了一些爆紅的信息,這些就是異常提示,這就是Java中提供的異常機制,當你的程序存在問(wèn)題的情況下,會(huì )給你打印輸出一些信息,這個(gè)就叫做異常信息。
字面意思上去理解,所謂“異?!币簿褪恰安徽!?,放在代碼程序中就是那些導致不能正確執行的問(wèn)題,比如上述代碼,Java就會(huì )給你打印出為啥此段代碼不能正確執行,給你輸出不正常的信息,這樣你就可以根據異常信息去修改代碼,從而提高代碼的健壯性!
以上我們簡(jiǎn)單看了下一個(gè)具體的異常,下面我們就“何為異?!痹僦卑椎奶接懸幌?,異常作為一種代碼的處理機制,現在基本上大多數的編程語(yǔ)言都包含有這個(gè)異常機制,但是,我門(mén)熟知的偉大的C語(yǔ)言是沒(méi)有異常處理機制的。
大多數的高級語(yǔ)言,比如Java,python,C++這些都包含非常完善的異常處理機制,既然有這個(gè)玩意,那自然有它的好處,一般來(lái)說(shuō)吧,擁有異常機制可以是我們的代碼:
那啥意思嘞?啥是容錯性,啥又是健壯呢?
首先是容錯性,這個(gè)通俗來(lái)講,就是可承受錯誤的范圍和概率,比如說(shuō)我們的程序要是沒(méi)有異常機制的話(huà),那很多錯誤是無(wú)法承受的,可能一旦出現錯誤,就會(huì )導致我們的系統崩潰出大問(wèn)題,這個(gè)帶來(lái)的后果可能比較嚴重,但是具有異常機制,就可以幫助我們去處理一些錯誤,以至于即使出現錯誤也不會(huì )造成這么嚴重的后果。
那什么又是健壯呢?這個(gè)一般就是說(shuō)我們的代碼比較安全,不容易出現bug,基本上把該想到的情況都想到了,代碼編寫(xiě)比較嚴謹,不容易出錯,質(zhì)量好,這個(gè)一般就可以說(shuō)我們的代碼比較健壯。
當然,以上只是我粗淺的理解,希望能夠幫助大家對異常機制的理解。
那再來(lái)說(shuō)異常,其實(shí)就是不好的東西,比如我們的代碼有bug,程序出錯等等,這些都是有可能發(fā)生的,誰(shuí)也不能保證自己寫(xiě)的代碼一定是正確的,對吧。
異常也就是代碼中可能出現的意外情況,這就要求我們在編寫(xiě)代碼的時(shí)候盡量考慮全面,但是即使你考慮的再全面也不可能將所有的意外情況都考慮進(jìn)去,所以,實(shí)際當中意外情況會(huì )有發(fā)生的概率,對于這種我們無(wú)法考慮周到的意外情況,就需要我們的異常機制去處理了。
接下來(lái)我們來(lái)看看Java中的異常,想必大家多多少少都會(huì )聽(tīng)說(shuō)過(guò)這樣一個(gè)異常叫做空指針異常,我們來(lái)看代碼演示:
NullPointerException nullPointerException = new NullPointerException("空指針異常"); System.out.println(nullPointerException);
可以發(fā)現,在Java真實(shí)存在NullPointerException這個(gè)類(lèi),而我們可以通過(guò)這個(gè)類(lèi)去創(chuàng )建具體的異常對象,比如這里的空指針異常對象,我們打印輸出看看:
如此來(lái)看,在Java中,異常是以類(lèi)的形式存在的,而且我們可以通過(guò)這些異常類(lèi)去創(chuàng )建相應的異常對象,那么我們再來(lái)看這段代碼:
public static void main(String[] args) { int a = 1; int b = 0; System.out.println(a / b); }
這里會(huì )出現異常,其實(shí)實(shí)際上就是在運行到“ System.out.println(a / b);”的時(shí)候Jvm虛擬機就會(huì )在底層為我們創(chuàng )建出一個(gè)異常對象,從而將相關(guān)的異常信息打印輸出。
所以:
Java中異常是的的確確存在的類(lèi)
接下來(lái)我們來(lái)說(shuō)說(shuō)Java的異常機制。我們還是來(lái)看上面那個(gè)代碼,也就是這個(gè):
public static void main(String[] args) { int a = 1; int b = 0; System.out.println(a / b); }
這段代碼我們如果運行的話(huà)是會(huì )出錯的,也就是這樣:
這里會(huì )給到我們一個(gè)異常信息,告訴我們說(shuō)除數不能為0,然后程序就自動(dòng)退出了,接下來(lái)我們再為這段代碼添加一個(gè)打印輸出:
很顯然這里并不會(huì )執行后面的這句輸出語(yǔ)句,因為前面已經(jīng)出現異常程序退出了,但是如果我們要求這里的輸出必須執行該怎么辦呢?
在Java中是提供了相對應的異常處理機制的,以上在a/b的時(shí)候出現了異常,在Java中我們是可以通過(guò)如下的方式去捕獲到這個(gè)異常的。
具體的操作就是使用try- catch去捕獲我們的異常并作出相應處理,具體看代碼:
public static void main(String[] args) { int a = 1; int b = 0; try { System.out.println(a / b); } catch (Exception e) { System.out.println(e+":除數不能為0!"); } System.out.println("執行到這里……"); }
我們在之前已經(jīng)說(shuō)過(guò),在Java中,異常是以類(lèi)的形式存在的,在我們寫(xiě)的程序代碼中,只要出現了異常,JVM就會(huì )給我們創(chuàng )建一個(gè)異常對象出來(lái),這個(gè)時(shí)候,我們是需要對這個(gè)異常對象做處理的,如果你放任不管的話(huà),最終導致的結果就是你的Java程序會(huì )退出。
所以啊,有異常你不處理,你的程序就會(huì )退出,那咋辦,處理啊,找到這個(gè)異常,處理它,那怎么找到呢?
我們可以使用try去包裹可能出現的異常代碼,比如上述所講的代碼,在執行到a/b的時(shí)候可能出現異常,也就是b不能為0,這里簡(jiǎn)單說(shuō)下,這里的a和b是我們提前定義還好的,如果是讓用戶(hù)輸入a和b的值呢?
我們簡(jiǎn)單改寫(xiě)下代碼:
Scanner StringA = new Scanner(System.in); Scanner StringB = new Scanner(System.in); int a = Integer.parseInt(StringA.next()); int b = Integer.parseInt(StringB.next()); System.out.println(a/b); System.out.println("執行到這里……");
這里的意思是我們從鍵盤(pán)輸入去獲取這個(gè)a和b,那么當我們輸入的是這樣的a和b的時(shí)候執行是沒(méi)什么問(wèn)題的:
可是一旦用戶(hù)不小心把b的值輸成0,那么問(wèn)題就來(lái)了:
所以這里繞了一圈就是告訴大家b/a這步操作是可能出現異常的,我們把這個(gè)操作叫做可能出現的異常代代碼塊,于是我們就可以使用try去操作這段代碼:
try { System.out.println(a / b); } catch (Exception e) { System.out.println(e+":除數不能為0!"); }
這里要注意的就是,這個(gè)try和catch是一起配合使用的,catch是捕獲的意思,我們使用try包裹可能出現的異常代碼快,然后使用catch去捕獲這個(gè)異常對象,然后做出相應的處理,比如這里,我們使用try包裹了a/b的操作,那么當b不小心被賦值為零的時(shí)候,那么這里在運行的時(shí)候就會(huì )出現異常,由于在Java中異常是以類(lèi)的形式存在,所以這里會(huì )拋出一個(gè)異常對象。
那么我們仔細看這個(gè)catch后面有個(gè)括號,就是異常對象參數,意思就是如果出現的這個(gè)異常對象屬于我括號里的這個(gè)異常,那么就進(jìn)入這個(gè)catch塊去處理這個(gè)異常。
說(shuō)白了就是,程序一旦出現異常,隨之而來(lái)就是會(huì )產(chǎn)生一個(gè)異常對象,而異常是以類(lèi)的形式存在,那么你就得為這個(gè)異常對象定義一個(gè)catch塊,這個(gè)異常對象會(huì )根據catch后的參數去找屬于自己的那一個(gè)catch塊,找到就進(jìn)入該catch塊,沒(méi)有的話(huà)程序就有因為異常而終止了。
而除數為0這是一個(gè)叫做ArithmeticException的異常,也就是算術(shù)異常,而這個(gè)異常是繼承自Exception,也就是說(shuō)Exception的范圍比ArithmeticException要大,所以Exception的catch塊可以處理身為子類(lèi)的ArithmeticException異常。
以上我們說(shuō)了ArithmeticException這個(gè)異常是繼承自Exception的,后者的范圍更廣,然后我們再看代碼:
try { System.out.println(a / b); } catch (ArithmeticException e) { System.out.println(e + ":除數不能為0!出現算術(shù)異常"); } catch (Exception e) { System.out.println(e+"出現異常"); }
也就是說(shuō),一個(gè)try下面可以對應多個(gè)catch塊,而每一個(gè)catch都有自己對應的一個(gè)可以處理的異常類(lèi)型,我們看上面的改進(jìn),我們添加了負責處理ArithmeticException的catch塊,那么結果是如呢?
可以看到,這里是直接進(jìn)入了ArithmeticException的catch塊,也就是說(shuō),異常對象一旦被一個(gè)catch捕獲,就不會(huì )再進(jìn)入下一個(gè)異常了。
有人可能會(huì )說(shuō)可以這樣嗎?
也就是把Exception放在第一個(gè)catch塊,實(shí)際上這里是不行的,為啥?我們來(lái)看下再這里的一個(gè)異常繼承關(guān)系:
可以看到,ArithmeticException其實(shí)是Exception的子類(lèi)的,如果你把Exception放在第一個(gè)catch塊的話(huà)那么所以的異常對象都將直接被這個(gè)catch塊捕獲,因為所有的異常對象都是Exception或者其子類(lèi)的實(shí)例對象,這就很關(guān)鍵啊,意味著(zhù)你后面定義再多的catch塊也沒(méi)有用啊,因為永遠不會(huì )執行到這里,上面已經(jīng)被Exception這個(gè)老大哥給截胡了,在Java中永遠執行不到的代碼就會(huì )被定義為錯誤的,所以是不能把Exception給放到第一個(gè)catch塊的。
這里有這么一個(gè)原則:
先捕獲處理小范圍的異常,再捕獲處理大范圍的異常,也就是先小后大
意思也就是先把子類(lèi)異常放在前面的catch塊,這么以來(lái),Exception的捕獲基本上都是在最后一個(gè)catch了。
這個(gè)是在Java 7之后增加的,也就是說(shuō)啊在Java7之前嘞,一般來(lái)說(shuō)一個(gè)catch塊只能捕獲處理一個(gè)異常,但是在Java7之后就升級了,可以一個(gè)catch塊捕獲處理多個(gè)異常。
那這個(gè)是怎么操作的呢?來(lái)看看代碼就一目了然了:
是不是還是比較清楚的,這里的編寫(xiě)也很簡(jiǎn)單,就是通過(guò)符號“|”把不同的異常對象類(lèi)型給分隔開(kāi),記住這里只需要在最后定一個(gè)異常就行,也就是這里的“e”,同時(shí)由于是捕獲多個(gè)異常,這里的e其實(shí)是默認final修飾的,因此就不能再對e進(jìn)行任何賦值操作了,比如一下這樣就是錯誤的:
這就是多個(gè)異常的捕獲了。
獲取異常信息
先明白這點(diǎn):
當產(chǎn)生一個(gè)異常對象,被相對應的catch塊捕獲之后,這個(gè)catch塊后的異常形參變量也就接受到了這個(gè)異常對象。
因此,我們就可以通過(guò)這個(gè)異常參數去獲得一些異常信息,一般我們常用的一些方法如下:
那具體的訪(fǎng)問(wèn),我們看下代碼便知:
這里其實(shí)也比較簡(jiǎn)單,就是幾個(gè)常見(jiàn)的異常信息獲取方法的使用。
這個(gè)可以說(shuō)叫做善后的,啥意思嘞?簡(jiǎn)單來(lái)說(shuō),就是你的異常對象無(wú)論進(jìn)入哪個(gè)catch塊執行,那么到最后這個(gè)finally里的代碼一定會(huì )被執行。
這個(gè)一般用在哪里呢?通常被用于釋放資源,一般比如說(shuō)數據庫連接操作,網(wǎng)絡(luò )連接或者常見(jiàn)的IO流的操作,這些就需要進(jìn)行資源的回收,那么這個(gè)時(shí)候就可以使用finally里,因為它必定會(huì )被執行。
看到這里不知道大家有沒(méi)有疑惑啊,不是說(shuō)Java會(huì )自動(dòng)回收資源嗎?這個(gè)感覺(jué)要手動(dòng)操作啊,這里其實(shí)你要區分資源的分類(lèi),Java的垃圾回收針對的堆內存中的對象所占用的內存,而這里說(shuō)的IO流操作,數據庫連接什么都是屬于物理資源,而物理資源必須是需要手動(dòng)回收的。
看看代碼:
這個(gè)時(shí)候我們看異常的處理就比較完整了,也就是包括try,然后是catch,再加上一定會(huì )被執行的finally塊。
那么這里就需要特別說(shuō)一下了:
對于異常處理來(lái)說(shuō),try塊是必須的,沒(méi)有try塊啥也不是,而catch和finally則不是必須的,但是,也必須選擇其一,也就是說(shuō),你不能只有個(gè)try,既沒(méi)有catch也沒(méi)有finally,然后就是注意catch塊了,可以有多個(gè),但是要遵循“先小后大”的原則
接下來(lái)我們來(lái)看個(gè)測試,看代碼:
我們在這里加入了return語(yǔ)句,一般來(lái)說(shuō)吧,只要程序的方法中碰到了return,那么就會(huì )立即結束該方法,但是現在呢?我們看下結果:
這說(shuō)明,finally語(yǔ)句一定會(huì )被執行!另外再給大家說(shuō)一個(gè)注意點(diǎn):
如果你在finally中定義了return語(yǔ)句,那么這個(gè)將導致你在try中定義的return語(yǔ)句失效,所以記住一點(diǎn),不要在finally中使用return哦。
到這里我們清楚了,對于finally語(yǔ)句來(lái)說(shuō)是一定會(huì )被執行的(其實(shí)有例外,比如你調用了System.exit(1)退出虛擬機),我們常在finally中去做釋放資源的操作,但是你有沒(méi)有發(fā)現,這樣的操作覺(jué)得比較麻煩😡,那有沒(méi)有簡(jiǎn)單的一些做法呢?
其實(shí)在Java7中對這個(gè)try語(yǔ)句進(jìn)行了增強,可以讓我們不需要在finally中進(jìn)行資源的關(guān)閉操作,可以自動(dòng)幫我們關(guān)閉需要釋放的資源,但是這里有個(gè)前提就是你所需要關(guān)閉的資源類(lèi)要么實(shí)現AutoCloseable接⼝,要么實(shí)現Closeable接⼝,實(shí)際上在Java7中幾乎把所有的資源類(lèi)都進(jìn)行了改寫(xiě),主要就是都實(shí)現了AutoCloseable或者Closeable接⼝,可以讓其實(shí)現資源的自動(dòng)關(guān)閉,這些資源類(lèi)一般就是文件IO的各種類(lèi),或者是JDBC的Connection接口等等。
那到了Java9之后又對這個(gè)try語(yǔ)句進(jìn)行了增強,在java7的改進(jìn)中你需要在try后的圓括號內聲明并創(chuàng )建資源,到了Java9,你不需要這樣做了,只需要自動(dòng)關(guān)閉的資源有final修飾或者是有效的final即可,這里先盡做了解,后期會(huì )詳細探討。
接下來(lái)我們來(lái)看看關(guān)于異常的分類(lèi),Java中的異??梢苑譃閮蓚€(gè)大類(lèi):
那怎么區分這兩類(lèi)異常呢?所有的RuntimeException類(lèi)及其⼦類(lèi)的實(shí)例被稱(chēng)為Runtime異常;不 是RuntimeException類(lèi)及其⼦類(lèi)的異常實(shí)例則被稱(chēng)為Checked異常。
那對于Checked異常就是可檢查異常,也就是說(shuō)在Java中認為這種異常是可以被提前處理的,所以一旦出現這種異常你就得處理它,如果不處理它,那是編譯都無(wú)法通過(guò)的。
那怎么去處理這個(gè)Checked異常呢?我們前面也說(shuō)了,可以使用try- catch的方式去捕獲處理異常,當然,我們還有一種方式就是拋出異常,暫且不管,這個(gè)等會(huì )會(huì )講。
對于Runtime異常也就是運行時(shí)異常了,這個(gè)我們不需要在編譯階段就處理它,如果要處理的話(huà),可以使用try- catch,就比如上面我們一直演示的那個(gè)除數為0的案例。
我們可以使用throws來(lái)聲明拋出異常,啥意思嘞,這個(gè)拋出異常咋回事?字面意思去理解,就是這個(gè)異常不管了,扔出去,對吧,拋出拋出,那如何扔出去呢?使用這個(gè)throws關(guān)鍵字即可。
也就是說(shuō)當你不知道該如何處理某一類(lèi)型的異常的時(shí)候,你就可以選擇將該異常拋出,實(shí)際上拋出異常也不是說(shuō)就不管異常了,而是將該異常交給上一級調用者去處理。如果一直往上拋出異常,最終就把這個(gè)燙手山芋交給了JVM,那JVM是怎么處理這個(gè)異常呢?
一般就是:
打印異常的跟蹤棧信息,并中止程序
下面我們來(lái)看下代碼:
我們這里在main方法上使用throws拋出了這個(gè)異常,那就是把這個(gè)異常扔給了我們的JVM,而JVM的處理上面也說(shuō)了,我們看下結果:
打印出跟蹤棧信息,然后中止程序,這里其實(shí)是個(gè)運行時(shí)異常,也就是Runtime異常,接下來(lái)我們看下對于Checked異常的拋出,我們首先編寫(xiě)一段含有Checked異常的代碼,如下:
這里就會(huì )產(chǎn)生一個(gè)編譯時(shí)異常,那么IDEA給我們的提示可以用try/catch捕獲處理,當然,也可以使用throws關(guān)鍵字拋出,我們這里將其拋出:
接下來(lái)我們在main方法中去調用這個(gè)方法:
發(fā)現了嗎?我們在main方法中調用它依然是需要處理出現的異常的,本身CheckedTest將異常拋出,就是希望由調用者去處理該異常,所以這里我們在main方法中去調用該方法的時(shí)候也要一并去處理該方法產(chǎn)生的異常,要不你繼續將其拋出交給JVM,要不使用try/catch捕獲!
這里給大家看一個(gè)示例:
發(fā)現沒(méi)有,當我們使用throws去拋出一個(gè)異常時(shí),父類(lèi)中的方法拋出一個(gè)異常,而其子類(lèi)中重寫(xiě)該父類(lèi)拋出異常的方法的時(shí)候,重寫(xiě)后的方法拋出的異常的范圍是不能比父類(lèi)中方法拋出的異常的范圍大的,這句話(huà)可能有點(diǎn)繞,但是配合看圖應該能明白什么意思。
這其實(shí)就是Checked異常所帶來(lái)的一個(gè)限制。
以上我們使用throw是來(lái)拋出異常其實(shí)都是Java自動(dòng)幫我們去拋出異常對象的,除此之外,我們還可以自己手動(dòng)的去拋出異常,這里需要使用到的一個(gè)關(guān)鍵字叫做throw,注意這里是沒(méi)有s的,和以上我們說(shuō)的throws是不一樣的。
想一下這里為什么要手動(dòng)拋出異常呢?因為異常本身就不是確定的,什么意思呢?就是同一件事情,在不同的人看來(lái)可能性質(zhì)就不一樣,比如你明天要外出,可是明天突然就下雨了,那么這個(gè)下雨對你來(lái)說(shuō)就是一種異常,是你不想要的,但是對于那些尤為某種情況希望明天下雨的來(lái)說(shuō),這件事情就不是一件異常事件。
對應到我們的程序中,異常也是要根據具體情況來(lái)定義的,因此這種異常是系統無(wú)法幫我們來(lái)判定的,這就需要我們自行去拋出異常。
具體就是使用throw來(lái)手動(dòng)拋出異常,怎么操作的看代碼:
try { //規定第一次輸入的值不能大于10,也就是這里的stringA不能大于10 Scanner stringA = new Scanner(System.in); Scanner stringB = new Scanner(System.in); int a = Integer.parseInt(stringA.next()); int b = Integer.parseInt(stringB.next()); if (a > 10) { throw new Exception("輸入的第一個(gè)數字不能大于10"); } else { System.out.println(a + b); } } catch (Exception e) { System.out.println(e.getMessage()); System.out.println("第一次輸入請輸入一個(gè)小于10的數字!"); }
同樣的,當你手動(dòng)的拋出一個(gè)異常的時(shí)候也是需要對這個(gè)異常進(jìn)行處理的,我們這里使用try/catch來(lái)捕獲處理該異常,看結果:
這里說(shuō)一點(diǎn),就是無(wú)論你是手動(dòng)拋出異常還是系統給我們拋出異常,在java中對異常的處理方式是不變的。也就是說(shuō)碰到Checked異常,要不使用throws將其拋出,要么使用try/catch語(yǔ)句塊捕獲處理。
一般來(lái)說(shuō)吧,我們不會(huì )去手動(dòng)拋出異常,當然,這里說(shuō)的異常指的是系統級別的異常,那除此之外,我們還可以自己自定義異常,代碼如下:
class MyException extends Exception { public MyException(){} public MyException(String msg) { super(msg); } }
以上我們就自定義了一個(gè)異常,自定義異常我們需要注意以下兩點(diǎn):
創(chuàng )建一個(gè)無(wú)參構造器創(chuàng )建一個(gè)帶有字符串參數的有參構造器
這里的字符串參數其實(shí)就是異常的具體描述信息,比如我們之前這樣定義一個(gè)異常:
一般的我們要是自定義異常的話(huà)最好就是有一個(gè)“見(jiàn)名知意”的程度,就是我看到你這個(gè)自定義異常類(lèi)名,大概知道這是一個(gè)什么異常。
以上我們就Java中的異常進(jìn)行了學(xué)習,不知道你發(fā)現沒(méi)有,我們對異常的學(xué)習其實(shí)主要就是在圍繞以下五個(gè)關(guān)鍵字:
trycatchfinallythrowsthrow
然后還有就是要注意Checked異常和Runtime異常,以上都是關(guān)于異常的基本知識,掌握這些,足以應付我們在日常工作學(xué)習中異常操作,至于更深層次的學(xué)習則需要我們在實(shí)際應用的去不斷的探索了,關(guān)于Java中的異常,我們就先介紹到這里。
到此這篇關(guān)于Java異常的文章就介紹到這了,更多相關(guān)Java異常內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站