異常處理升級版
其實前面就寫了一篇異常處理的文章,但是那個文章實在是感覺太詳細了,不太好復習。所以今天我就再寫一篇這樣就更好復習了。
一、異常概述
在我們日常生活中,有時會出現各種各樣的異常,例如:職工小王開車去上班,在正常情況下,小王會準時到達單位。但是天有不測風雲,在小王去上班時,可能會遇到一些異常情況,比如小王的車子出了故障,小王只能改為步行.
異常指程序運行中出現的不期而至的各種狀況,如:文件找不到、網絡連接失敗、非法參數等。
異常發生在程序運行期間,它影響了正常的程序執行流程
Java通過API中Throwable類的眾多子類描述了各種不同的異常。Java中的異常都是對象,都是Throwable子類的實例。
每種異常類型都代表了一個錯誤的情況。
例如:
java.lang.ArrayIndexoutofBoundsException類,表示數組的下標在使用中超過了邊界
java.lang.ClassCastException類,表示類型轉換出現了錯誤
二、Error和Exception概述
在Java中,所有的異常都有一個共同的父類Throwable,該類有兩個重要的子類:Exception和Error,二者都是Java異常處理的重要子類,各自都包含大量子類。
它們都是java.lang下的類
java.lang.Throwable
java.lang.Exception
2.1、Error
這個是程序中發生的錯誤,是程序無法處理的,表示運行應用程序中較嚴重問題。而且很多錯誤與代碼編寫者執行的操作無關,而是表示代碼運行時JVM出現了問題。
例如,Java虛擬機運行錯誤(VirtualMachineError),當JVM中內存不足時,將出現 OutOfMemoryError。這些error發生時,JVM一般會選擇線程終止。
這些錯誤表示故障發生於虛擬機自身、或者發生在虛擬機試圖執行應時
如Java虛擬機運行錯誤(VirtualMachineError)、類定義錯誤(NoClassDefFoundError)等。這些錯誤一般是不可查詢的,因為它們在應用程序的控制和處理能力之外。
對於設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況,因為這是超出程序處理能力的。
2.2、Exception
這個是程序中發生的異常,是程序本身可以處理的,並且處理完之後程序本身還可以繼續往下執行。
我們進行拋出或捕獲的異常就是這個Exception類型及其子類型。
三、異常的拋出與捕獲
3.1、異常的拋出
在類中編寫方法的時候,這個方法中將來被執行的代碼如果有可能出現異常情況,那麽就"可以"在方法的參數列表後聲明該方法中可能會拋出的異常類型.
public class Test{ public void run()throws IOException,SQLException{ //.. } }
註意:
1)如果有多個異常類型要拋出,那麽需要使用逗號隔開.
2)所聲明拋出的異常是該方法執行後"可能"會出現異常類型
3)異常拋給了方法的調用者,誰調用的這個方法誰就負責處理這些異常
3.2、異常捕獲
當我們調用了一個方法,該方法在聲明的時候拋出了異常,那麽我們作為方法的調用者就必須去處理這些被拋出的異常。
例如:
Class類中的forName方法的聲明
public static Class<?> forName(String className)throws ClassNotFoundException
說明該方法在執行的時候有可能拋出ClassNotFoundException類型的異常,表示要加載的類找不到。
我們調用這個方法的時候,就需要對這個拋出的異常進行處理。
第一種方式:繼續把這個異常拋出去
public static void main(String[] args)throws ClassNotFoundException{ Class.forName("test...."); }
在main方法中調用forName方法時候,我們並沒有直接處理這個拋出的異常,而是繼續把該異常往上拋出,拋給main方法的調用者。
第二種方式:使用try-catch語句塊把拋出的異常進行捕獲
public static void main(String[] args) { try { Class.forName("test..."); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
分析:
1)try-catch是嘗試著去捕獲這些代碼拋的異常,如果try語句塊中的代碼沒有拋出異常,那麽try-catch是沒有任何作用的
2)如果try語句塊中的代碼拋出了異常,並且拋出的異常還是catch語句要處理的異常或其子類型異常,那麽這時就會執行catch語句塊中的代碼,進行異常出現後的處理。
3)異常對象e常用的方法
e.printStackTrace()
引用出堆棧中出現的異常跟蹤信息
e.getMessage()
返回異常的詳細字符串信息(如果有的話)
4)不管方法聲明中拋出了什麽類型的異常,我們一般都是可以再catch中使用Exception類型進行捕獲到的,因為Exception是所有異常的父類型。
例如:
try { Class.forName("test..."); .... .. } catch (Exception e) { e.printStackTrace(); }
5)如果代碼中拋出了多種異常,也可以使用多個catch來分別捕獲.當然也可以只使用一個最大的異常Exception
try { Class c = Class.forName("test.."); c.getMethod("go"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); }
3.3、throw和throws的區別
throw關鍵字是用於方法體內部,用來拋出一個Throwable類型的異常。如果拋出了檢查異常,則還應該在方法頭部聲明方法可能拋出的異常類型。
該方法的調用者也必須檢查處理拋出的異常。如果所有方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是打印異常消息和堆棧信息
public static void test() throws Exception { throw new Exception("方法test中的Exception"); }
throws關鍵字用於方法體外部的方法聲明部分,用來聲明方法可能會拋出某些異常。僅當拋出了檢查異常,該方法的調用者才必須處理或者重新拋出該異常。當方法的調用者無力處理該異常的時候,應該繼續拋出.
四、拋出和捕獲對程序的影響
4.1、如果程序中的某行代碼的執行拋出了異常,並且這個異常一種都沒有被try-catch處理,那麽這個異常最終會拋給JVM,JVM輸出異常信息後就自動停止了
public static void main(String[] args) throws ClassNotFoundException { System.out.println("hello"); Class.forName("test.."); System.out.println("world"); //....其他代碼 }
最終的結果是代碼在調用forName方法拋出異常後,JVM處理後就停止了.並沒有往下繼續執行代碼
4.2、如果使用try-catch語句去處理代碼中拋出的異常,那麽catch語句塊處理完之後,代碼還會在catch語句塊下面繼續執行
public static void main(String[] args){ System.out.println("hello"); try { Class.forName("test.."); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println("world"); //....其他代碼 }
最終的結果是catch語句執行完後,代碼繼續往下正常執行。
4.3、try-catch語句塊雖然能處理完異常後繼續讓代碼往下執行,但是在某些時候也會改變代碼的執行流程(默認是從上往下順序執行)
public static void main(String[] args){ System.out.println("hello"); try { Class.forName("test.."); System.out.println("world"); } catch (ClassNotFoundException e) { e.printStackTrace(); } //....其他代碼 }
最終的結果是catch語句執行完後,代碼執行下面的其他代碼,但是上面打印world的語句就跳過去了。
五、finally語句塊
由於異常處理有時候會改變程序的正常流程,這會使得某些不管在任何情況下都必須執行的步驟被忽略,從而影響程序的健壯性。
舉例:
小王開了一家店,在店裏上班的正常流程為:打開店門、工作8個小時、關門。異常流程為:小王在工作時突然犯病,因而提前下班
public void work() { try { 開門(); 工作8個小時(); 關門(); } catch(Exception e) { 去醫院(); } }
小王在工作時突然犯病,那麽流程會跳轉到catch代碼塊,這意味著關門的操作不會被執行,這樣的流程顯然是不安全的,必須確保關門的操作在任何情況下都會被執行.
finally代碼塊能保證特定的操作總是會被執行,它的形式如下:
public void work() { try { 開門(); 工作8個小時(); } catch(Exception e) { 去醫院() } finally { 關門(); } }
註:即使方法中執行了return語句,finally最後也是會被執行的。
try、catch、finally三個語句塊應註意的問題
1)try、catch、finally三個語句塊均不能單獨使用,三者可以組成 try...catch...finally、try...catch、try...finally三種結構,catch語句可以有一個或多個,finally語句最多一個。
2)try、catch、finally三個代碼塊中變量的作用域為代碼塊內部,分別獨立而不能相互訪問。如果要在三個塊中都可以訪問,則需要將變量定義到這些塊的外面。
3)多個catch塊時候,最多只會匹配其中一個異常類且只會執行該catch塊代碼,而不會再執行其它的catch塊,且匹配catch語句的順序為從上到下,也可能所有的catch都沒執行。
4)先Catch子類異常再Catch父類異常。
圖解:
六、編譯時異常和運行時異常
1)Exception有一個特殊的子類:RuntimeException
2)RuntimeException類型及其子類型都是屬於運行時異常
3)其他類型的異常只要不是繼承了RuntimeException類的,都屬於編譯異常
4)編譯異常又稱checked異常,運行時異常又稱unchecked異常
因為編譯器在編譯期間如果遇到了checked異常,那麽是一定會提示我們,讓我們去處理的。但是如果遇到了unchecked異常,編譯器是不做任何事情的。
6.1、常見的運行時異常:unchecked
java.lang.ArithmeticException
算術異常
java.lang.NullPointerException
空指針引用
java.lang.ArrayIndexoutofBoundsException
數組越界
java.lang.ClassCastException
強制類型轉換異常
java.lang.NumberFormatException
數據格式異常
java.lang.NegativeArraySizeException
數組長度為負數異常
6.2、常見的運行時異常:checked
編譯器提示你需要處理的都為編譯異常
java.lang.ClassNotFoundException
java.lang.DataFormatException
java.lang.NoSuchMethodException
java.io.IOException
java.sql.SQLException
七、自定義異常
在需要的情況下,可以通過擴展Exception類或RuntimeException類來創建自定義的異常(一般是擴展Exception類)。異常類包含了和異常相關的信息,這有助於負責捕獲異常的catch代碼塊,正確地分析並處理異常。
例如:我們任務在系統中用戶要登錄的賬號和密碼不匹配就是一種異常情況,但是JDK中並沒有定義這種異常,所以我們可以進行自定義。
例如: 只需繼承Exception即可.一般還會加入和父類中匹配的構造器
public class UserPasswordException extends Exception{ public UserPasswordException() { super(); } public UserPasswordException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public UserPasswordException(String message, Throwable cause) { super(message, cause); } public UserPasswordException(String message) { super(message); } public UserPasswordException(Throwable cause) { super(cause); } }UserPasswordException
註:異常是可以在代碼中主動拋出的
public void login(String password)throws UserPasswordException{ if("123".equals(password)){ throw new UserPasswordException("密碼錯誤"); } }userUserPasswordException
UserPasswordException
八、斷言
8.1、斷言使用
在JDK1.4中,Java語言引入一個新的關鍵字: assert 該關鍵字有兩種形式:
assert 條件
以及
assert 條件:值或表達式
這兩種形式都會對條件進行評估,如果結果為false則拋出AssertionError。
在第二種形式中,值或表達式的值會傳入AssertionError的
構造器並轉成一個消息字符串,成為要顯示的錯誤信息
例如:
要斷言x不是負數,只需要使用如下簡單的語句:
assert x >= 0;
或者你可以將x的值傳遞給AssertionError對象,從而可以在報錯時候顯示:
assert x >= 0 : x;
或者
assert x >= 0 : "x的值不符合條件:x="+x;
8.2、斷言內容代碼編譯
因為assert在JDK1.4中是一個新的關鍵字,因此在使用時需要告訴編譯器你編譯所使用jdk的版本號。
javac -source 1.4 MyClass.java
在jdk的後續版本中,對斷言的支持成為默認特性(JDK5.0以上使用時就不需要這個編譯了,因為默認就支持的)。
8.3、斷言內容代碼編譯
因為assert在JDK1.4中是一個新的關鍵字,因此在使用時需要告訴編譯器你編譯所使用jdk的版本號。
javac -source 1.4 MyClass.java
在jdk的後續版本中,對斷言的支持成為默認特性(JDK5.0以上使用時就不需要這個編譯了,因為默認就支持的)。
註意:使用eclipse運行代碼的時候也是可以傳參數的(包括倆種參數)
例如:
java -xx com.briup.ch07.Test yy
xx是給JVM傳的參數 yy是給Test類的main方法傳的參數
異常處理升級版