您好,登錄后才能下訂單哦!
這篇文章給大家介紹一文帶你快速讀懂Java中的異常,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
什么是異常?
異常是Java語言中的一部分,它代表程序中由各種原因引起的“不正常”因素。 那么在程序中什么樣的情況才算不正常呢? 我認為可以這樣定義:如果出現了這么一種情況,它打斷了程序期望的執行流程,改變了控制流的方向(包括讓JVM停掉),那么就可以認為發生了不正常情況,也就是引發了異常。舉個例子顯而易見的例子:
FileOutputStream out = null; try { out = new FileOutputStream("abc.text"); out.write(1); System.out.println("寫入成功"); } catch (FileNotFoundException e) { System.out.println("要寫入的文件不存在"); e.printStackTrace(); } catch (IOException e) { System.out.println("發生了IO錯誤"); e.printStackTrace(); }finally{ if(out != null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } }
我調用FileOutputStream.write(int)方法期望向一個文件寫入一個字節的數據,如果在寫入時發生了IO錯誤, 那么就發生了“不正常情況”,也就是拋出IOException,進而程序的控制流發生了改變,本來如果寫入成功的話, 會執行FileOutputStream.write(int)下一句代碼, 現在發生了異常, 那么程序要跳到IOException對應的catch塊中,去處理這個異常情況。
異常體系和分類
Java以面向對象的方式來管理異常情況,也就是說,Java程序執行時遇到的各種問題都被封裝成了對象,并且這些對象之間具有繼承關系。java中的讓人不爽的“不正常情況”可以分為兩種,一種叫做Error,一種是在程序中到處可見的Exception,而他們都繼承自Throwable。Exception又分為編譯時受檢查異常(Checked Exception)和運行時異常(RuntimeException)。如下圖所示(該圖片來源于網絡):
一般情況下,Error代表虛擬機在執行程序時遇到嚴重問題,不能再回復執行了,這屬于重大事故,虛擬機要掛掉的,一句話概括就是“這病沒得治,等死就行了”。那么打開JDK的文檔,列舉幾種Error:
VirtualMachineError: 當 Java 虛擬機崩潰或用盡了它繼續操作所需的資源時,拋出該錯誤。
ClassFormatError:當 Java 虛擬機試圖讀取類文件并確定該文件存在格式錯誤或無法解釋為類文件時,拋出該錯誤。
NoClassDefFoundError:當 Java 虛擬機或 ClassLoader 實例試圖在類的定義中加載,但無法找到該類的定義時,拋出此異常。
而相對于Error,Exception是java程序中遇到的“不那么嚴重”的問題,這種問題是可以處理的,當處理了這個問題后,程序還可以繼續執行。一句話概括,“這是病,得治,這病是可以治好的”。
Exception就比較常見了,隨便舉幾個例子。當創建文件輸入流時, 發現文件不存在,那么拋出FileNotFoundException,但是異常可以處理,沒法讀文件,并不會在很大長度上影響整個程序的執行,畢竟不能讀文件,程序還可以執行其他邏輯。下面舉一個趣味性的示例:
public class Travel { private static int power = 100; private static boolean bridgeIsOk = true; public static void main(String[] args) { //描述一下坐火車旅游的過程 System.out.println("從濟南出發, 到北京旅游"); System.out.println("列車開到德州"); //中途給媽媽打個電話 try{ telToMom(); }catch(BatteryDiedException e){ System.out.println("換一塊電池, 繼續旅程"); } //橋斷了 if(!bridgeIsOk){ System.out.println("旅程結束"); throw new BridgeBreakError("橋斷了,列車停止運行"); } System.out.println("到北京站,下車"); //下雨了 try{ throw new RainException("下雨了"); }catch(RainException e){ System.out.println("撐起準備的雨傘, 繼續旅程"); } } private static void telToMom() throws BatteryDiedException{ if(power == 0){ //手機電量為0 System.out.println("手機沒電了"); throw new BatteryDiedException("手機沒電了"); } System.out.println("給媽媽打電話"); } static class BatteryDiedException extends Exception{ public BatteryDiedException(String msg){ super(msg); } } static class BridgeBreakError extends Error{ public BridgeBreakError(String msg){ super(msg); } } static class RainException extends Exception{ public RainException(String msg){ super(msg); } } }
上面的代碼描述了一次旅行, 如果在旅途中給媽媽打電話,發現手機沒電了, 拋出BatteryDiedException,但是這種異常是可以應付的,直接換一塊準備的備用電池就OK了,下了車之后,天下雨了,拋出RainException,這種異常也可以應付,因為提前準備了雨傘。這兩種情況都是可以恢復的,遇到之后,只需做一定的處理,旅程還能繼續。如果在途中遇到橋斷裂的情況,那么列車必須停止運行,這次旅行就泡湯了,也就是說已經不能從這種惡劣情況中恢復過來,所以直接拋出BridgeBreakError。
編譯時受檢查異常和運行時異常
那么再說一下編譯時受檢查異常和運行時異常。回顧一下異常的定義:程序在執行時遇到的不正常情況。那么既然是運行時遇到的問題,怎么還有一個編譯時受檢查異常呢?其實編譯時根本不會發生異常,只會在語法錯誤的情況下編譯失敗,但是這和異常是不相關的概念。異常只是運行時的行為。那么編譯時受檢查異常又是一個什么概念呢?要理解受檢查異常存在的意義,那么必須明確編碼者所處的位置,也可以說編碼者的角色, 即:我是功能的具體實現者, 還是功能的使用者,也可以說,我是方法的編寫者還是已有方法的調用者。如果我是方法的實現者,我在編碼時發現可能會出現異常,那么首先我要明確,這個可能出現的異常我能不能自己處理,如果能自己處理, 那么就在方法內部自己處理掉,如果不能自己處理,那么通知方法的調用者處理。舉例說明:
public static Class<?> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader()); }
上面的代碼是JDK中Class類的forName()方法。作為JDK類庫的作者,在寫這個方法的時候,可能會出現異常, 也就是類加載不到。但是他不知道如何處理這個情況,因為他不知道調用這個方法的用戶是加載的什么類,可能是一個非常重要的類, 加載不成的話程序就只能停掉,也可能是一個不那么重要的類,加載不到也沒有嚴重影響。所以,如何處理這個情況,必須是由用戶決定。方法后面的throws ClassNotFoundException的意義是:這個方法可能出現ClassNotFoundException,你如果調用了這個方法,那么必須做好防范措施(用try-catch處理這個異常,或者再向上拋出)。如果站在方法使用者的角度,我調用這個方法,如果出現異常,我可以提前準備好解決方案:
try { Class clazz = Class.forName("com.bjpowernode.Person"); } catch (ClassNotFoundException e) { System.out.println("Person類加載失敗"); System.exit(0); e.printStackTrace(); }
Person類是一個非常中要的類,必須加載成功才能繼續執行。如果加載失敗, 只能讓程序停掉,并且打印出日志。這樣的話,程序員可以在其他地方確保這個類必須是可加載的。
所以,可以把編譯時受檢查異常看做一種錯誤預警機制:這個錯誤可能發生, 但也可能不發生,但是如果你想使用這個功能的話,必須做好處理措施,可以使用try-catch處理異常, 也可以拋向更高層。
說完了編譯時受檢查異常,那么在談運行時異常, 所有運行時異常的頂層父類都是RuntimeException, RuntimeException也是繼承自Exception的。下面是JDK文檔中對運行時異常的解釋。
1.RuntimeException 是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類。
3.可能在執行方法期間拋出但未被捕獲的 RuntimeException 的任何子類都無需在 throws 子句中進行聲明。
也就是說, 如果你在方法中拋出了運行時異常或者其子類,那么可以不必在方法上聲明會拋出異常,所以調用這個方法的調用者也就不必在使用的時候做預防措施。那么在異常發生的時候,由于沒有處理措施,那么只能讓虛擬機停掉,也就是說這種異常一般不需要提前預防。那么什么時候使用運行時異常呢?可以這樣認為:如果發生了這樣一個異常時,讓程序停掉是合理的,那么這種情況就適合使用運行時異常。
還是以上面旅行的例子做一個說明。如果手機在旅途沒電了,那么預防這種情況是有意義的,因為換了電池之后還可以繼續旅行;突然下雨這種情況也可預防,并且預防這種情況是有意義的,因為打起傘來同樣可以繼續前進。那么,如果如果在旅途中病了,并且病的還很厲害,那么再預防這種情況對整個旅程來說就沒有什么意義了,因為旅程必須終止(看病要緊)。所以直接拋出一個運行時異常讓旅程終止。如下:
private static boolean isSick = true; public static void main(String[] args) { if(isSick){ System.out.println("生病了,旅途中止"); throw new SickException("病了"); } } private static class SickException extends RuntimeException{ public SickException(String msg){ super(msg); } }
一般來說,運行時異常非常適合處理編程錯誤,那么什么是編程錯誤呢?可以認為是程序員寫的代碼有問題,必須修改程序才能解決問題。看一下JDK中的兩個RuntimeException的例子。
IllegalArgumentException:如果用戶(方法的調用者)傳遞的參數不對,那么就會拋出非法參數異常,然后讓程序停掉,如果想讓程序正確的運行,必須修改調用方式,傳遞一個正確的參數。如下:
public static void main(String[] args) { caculateSalary(3); } /** * 計算一個月的薪資 * @param month 月份 */ public static void caculateSalary(int month){ //如果參數錯誤, 拋出非法參數異常 if(month < 1 || month > 12){ throw new IllegalArgumentException(); } } private static void caculateSalaryInner(int month){ //計算薪資 ... }
NullPointerException:如果調用一個方法的對象為null,那么在調用的時候會拋出空指針異常。如果要避免的話,就要修改程序,確保調用方法的對象不為空。
ClassCastException:如果在進行類型轉換時,指定了錯誤的目標類型,那么會拋出類型轉換異常。如果要避免的話,要修改代碼,以確保指定了正確的要轉換的目標類型。
雖然RuntimeException一般用于表示編程錯誤,在拋出運行時異常時讓程序停掉,對代碼做一定的改正以讓程序可以再次正確運行, 但是要注意到,運行時異常是可以捕獲的,捕獲之后做出處理后,程序可以恢復執行:
public static void main(String[] args) { doSomething(); } public static void doSomething(){ Object obj = null; try { //運行時異常也是可以捕獲的 obj.toString(); } catch (RuntimeException e) { System.out.println("拋出了運行時異常, 異常的具體類型:" + e.getClass().getName()); } }
打印結果為: 拋出了運行時異常, 異常的具體類型:java.lang.NullPointerException
另外,運行時異常也可以在方法上聲明拋出,但是如果方法上聲明的是運行時異常,那么方法的調用者可以選擇處理, 也可以選擇不處理。如果不處理的話,程序會終止,如果捕獲后做出處理,程序可以恢復運行:
public static void main(String[] args) { doSomething(); //不必處理方法聲明拋出的運行時異常 } public static void doSomething() throws RuntimeException{ throw new RuntimeException(); }
雖然運行時異常可以在方法上聲明拋出,也可以被捕獲,但是一般情況下我們不會這么做。因為運行時異常一般用于表示編程錯誤,出現異常時讓程序停掉是合理的。對運行時異常進行捕獲和聲明拋出沒有多大的意義。比如捕獲了空指針異常,雖然進行了處理以讓程序不至于崩潰,但是空對象要調用的方法,根本就沒有調用成功,這是不合理的。
如何合理使用異常
上面介紹了異常的定義和分類,也提到了一些異常的使用原則。現在總結一下到底應該如何使用異常:
1 重大的錯誤使用Error。一般Error用于表示系統級別的或虛擬機層面上的錯誤,在編程中很少使用。
2 有必要預防,并且處理后可以讓程序恢復執行的情況使用編譯時受檢查異常。
3 編程錯誤使用運行時異常。
4 如果方法自己可以處理異常,那么可以選擇自己處理異常,如果方法不知道如何處理異常,那么拋給高層的方法調用者。
5 方法聲明拋向高層的異常,必須是對高層有意義并且高層能夠理解的異常。
下面再舉一個趣味性的例子。
老板派員工出去執行一項任務,在這個過程中有兩個角色,員工是低層被調用者,老板是高層調用者。在這個過程中可能出現這么幾種情況。
1 老板讓員工出去執行一項任務, 那么必須得給撥款(沒錢干不成事嘛)。那么如果老板沒給錢,或者給的錢不夠,那么員工可以選擇停止執行。這屬于編程錯誤,要求老板必須給足夠的錢才能繼續運行。這種情況使用運行時異常表示。
2 到了目的地后,要去辦公地點,發現迷路了(可能方向感不好,轉向了),找不到公交車的站牌了。這個錯誤自己完全可以解決,打個車就可以了。并且不能拋給老板,如果拋給老板,那么就等著被炒魷魚吧。老板每天很忙,他會這樣認為:這員工太操蛋了,這點事都辦不成。所以這是個受檢查異常,并且適合在內部解決。
3 出差在外,加班太辛苦,在干到一半的時候,累病了。這就是比較嚴重的情況了,自己不能很好地解決(得去醫院)。這也是可以預見的異常,畢竟人都會得病嘛。這也屬于受檢查異常,自己不能解決,得拋向高層(老板)。但是應該怎樣給老板說呢?不能給老板說“老板,我病了”,如果這樣給老板說的話,老板會一頭霧水:“病了去醫院啊, 我又不是醫生”。那么怎么給老板說呢?直接告訴老板任務不能完成就行了,當然可以說明為什么不能完成的原因(生病了)。這樣的話,老板就可以做出一些處理,可以另外再拍一個人去交接任務,也可以決定暫停任務。所以,拋向高層的異常,必須是對高層有意義并且高層能夠理解的異常。
下面用代碼描述這個過程:
public class DoWork { public static class Boss{ //老板 private Employee emp; //員工對象 public Boss(Employee emp){ this.emp = emp; } public void doWork(){ try { emp.doWork(); //老板委托員工外出執行任務,給員工塊錢的經費 } catch (TaskCannotCompleteException e) { //任務無法完成 System.out.println("派出另一個員工去完成任務"); } } } public static class Employee{ //員工 //執行任務,可能不能完成任務 public void doWork(float money) throws TaskCannotCompleteException{ // if(money < ){ //經費太少,無法執行任務 throw new MoneyNotEnoughException(); } // try { goToWorkPlace(); } catch (CannotFindBusException e) { //在去工作地點時找不到公交車 System.out.println("打車去"); } // try { workDayAndNight(); } catch (TiredToSickException e) { //累病了 //告訴老板,任務無法完成 throw new TaskCannotCompleteException(); } } //在去工作地點時可能找不到公交車 private void goToWorkPlace() throws CannotFindBusException{ //throw new CannotFindBusException(); } //沒天沒夜的干活, 可能會累病 private void workDayAndNight() throws TiredToSickException{ //throw new TiredToSickException(); } } //找不到公交車異常 public static class CannotFindBusException extends Exception{} //經費不足異常 public static class MoneyNotEnoughException extends RuntimeException{} //累病異常 public static class TiredToSickException extends Exception{} //任務無法完成異常 public static class TaskCannotCompleteException extends Exception{} public static void main(String[] args) { Boss boss = new Boss(new Employee()); boss.doWork(); } }
關于一文帶你快速讀懂Java中的異常就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。