Java學習_異常處理
- Java的異常
-
計算機程式執行的過程中,總是會出現各種各樣的錯誤。有一些錯誤是使用者造成的,比如,希望使用者輸入一個
int
型別的年齡,但是使用者的輸入是abc。程式想要讀寫某個檔案的內容,但是使用者已經把它刪除了。
還有一些錯誤是隨機出現,並且永遠不可能避免的。比如:- 網路突然斷了,連線不到遠端伺服器;
- 記憶體耗盡,程式崩潰了;
- 使用者點“列印”,但根本沒有印表機;
- ……
-
Java內建了一套異常處理機制,總是使用異常來表示錯誤。異常是一種
class
,因此它本身帶有型別資訊。異常可以在任何地方丟擲,但只需要在上層捕獲,這樣就和方法呼叫分離了。1 try { 2 String s = processFile(“C:\\test.txt”);
- Java的異常是
class
,它的繼承關係如下: - 從繼承關係可知:
Throwable
是異常體系的根,它繼承自Object
Throwable
有兩個體系:Error
和Exception
,Error
表示嚴重的錯誤,程式對此一般無能為力。 Exception
則是執行時的錯誤,它可以被捕獲並處理。-
Exception
又分為兩大類:RuntimeException
以及它的子類;- 非
RuntimeException
(包括IOException
、ReflectiveOperationException
等等)
- Java規定:
-
必須捕獲的異常,包括
Exception
及其子類,但不包括RuntimeException
及其子類,這種型別的異常稱為Checked Exception。 -
不需要捕獲的異常,包括
Error
及其子類,RuntimeException
-
- 編譯器對RuntimeException及其子類不做強制捕獲要求,不是指應用程式本身不應該捕獲並處理RuntimeException。是否需要捕獲,具體問題具體分析。
-
捕獲異常
-
捕獲異常使用
try...catch
語句,把可能發生異常的程式碼放到try {...}
中,然後使用catch
捕獲對應的Exception
及其子類。1 import java.io.UnsupportedEncodingException; 2 import java.util.Arrays; 3 4 public class Main { 5 public static void main(String[] args) { 6 byte[] bs = toGBK("中文"); 7 System.out.println(Arrays.toString(bs)); 8 } 9 10 static byte[] toGBK(String s) { 11 try { 12 // 用指定編碼轉換String為byte[]: 13 return s.getBytes("GBK"); 14 } catch (UnsupportedEncodingException e) { 15 // 如果系統不支援GBK編碼,會捕獲到UnsupportedEncodingException: 16 System.out.println(e); // 列印異常資訊 17 return s.getBytes(); // 嘗試使用用預設編碼 18 } 19 } 20 }
- 如果我們不捕獲
UnsupportedEncodingException(去掉try catch)
,會出現編譯失敗的問題。編譯器會報錯,錯誤資訊類似:unreported exception UnsupportedEncodingException; must be caught or declared to be thrown,並且準確地指出需要捕獲的語句是return s.getBytes("GBK");
。意思是說,像UnsupportedEncodingException
這樣的Checked Exception,必須被捕獲。 - 這是因為
String.getBytes(String)
方法定義是(如下),在方法定義的時候,使用throws Xxx
表示該方法可能丟擲的異常型別(Runtime及其子類除外)。呼叫方在呼叫的時候,必須強制捕獲這些異常,否則編譯器會報錯。public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { ... }
- 在
toGBK()
方法中,因為呼叫了String.getBytes(String)
方法,就必須捕獲UnsupportedEncodingException
。我們也可以不捕獲它,而是在方法定義處用throws表示toGBK()
方法可能會丟擲UnsupportedEncodingException
,就可以讓toGBK()
方法通過編譯器檢查。1 import java.io.UnsupportedEncodingException; 2 import java.util.Arrays; 3 4 public class Main { 5 public static void main(String[] args) { 6 byte[] bs = toGBK("中文"); 7 System.out.println(Arrays.toString(bs)); 8 } 9 10 static byte[] toGBK(String s) throws UnsupportedEncodingException { 11 return s.getBytes("GBK"); 12 } 13 }
- 只要是方法宣告的Checked Exception,不在呼叫層捕獲,也必須在更高的呼叫層捕獲。所有未捕獲的異常,最終也必須在
main()
方法中捕獲,不會出現漏寫try
的情況。這是由編譯器保證的。main()
方法也是最後捕獲Exception
的機會。 - 如果是測試程式碼,上面的寫法就略顯麻煩。如果不想寫任何
try
程式碼,可以直接把main()
方法定義為throws Exception。
因為main()
方法聲明瞭可能丟擲Exception
,也就聲明瞭可能丟擲所有的Exception
,因此在內部就無需捕獲了。代價就是一旦發生異常,程式會立刻退出。 - 在
toGBK()
內部“消化”異常static byte[] toGBK(String s) { try { return s.getBytes("GBK"); } catch (UnsupportedEncodingException e) { // 什麼也不幹 } return null;
} - 這種捕獲後不處理的方式是非常不好的,即使真的什麼也做不了,也要先把異常記錄下來:
static byte[] toGBK(String s) { try { return s.getBytes("GBK"); } catch (UnsupportedEncodingException e) { // 先記下來再說: e.printStackTrace(); } return null;
} - 所有異常都可以呼叫
printStackTrace()
方法列印異常棧,這是一個簡單有用的快速列印異常的方法。
-
- 捕獲異常
- 凡是可能丟擲異常的語句,都可以用
try ... catch
捕獲。把可能發生異常的語句放在try { ... }
中,然後使用catch
捕獲對應的Exception
及其子類。 -
可以使用多個
catch
語句,每個catch
分別捕獲對應的Exception
及其子類。JVM在捕獲到異常後,會從上到下匹配catch
語句,匹配到某個catch
後,執行catch
程式碼塊,然後不再繼續匹配。簡單地說就是:多個
catch
語句只有一個能被執行。 - 存在多個
catch
的時候,catch
的順序非常重要:子類必須寫在前面。 -
無論是否有異常發生,都希望執行一些語句,例如清理工作,可以把執行語句寫若干遍:正常執行的放到
try
中,每個catch
再寫一遍。1 public static void main(String[] args) { 2 try { 3 process1(); 4 process2(); 5 process3(); 6 System.out.println("END"); 7 } catch (UnsupportedEncodingException e) { 8 System.out.println("Bad encoding"); 9 System.out.println("END"); 10 } catch (IOException e) { 11 System.out.println("IO error"); 12 System.out.println("END"); 13 } 14 }
- Java的
try ... catch
機制還提供了finally
語句,finally
語句塊保證有無錯誤都會執行。上述程式碼可以改寫如下。public static void main(String[] args) { try { process1(); process2(); process3(); } catch (UnsupportedEncodingException e) { System.out.println("Bad encoding"); } catch (IOException e) { System.out.println("IO error"); } finally { System.out.println("END"); } }
-
finally
有幾個特點:finally
語句不是必須的,可寫可不寫;finally
總是最後執行。
- 某些情況下,可以沒有
catch
,只使用try ... finally
結構。void process(String file) throws IOException { try { ... } finally { System.out.println("END"); } }
因為方法聲明瞭可能丟擲的異常,所以可以不寫
catch
。
- 凡是可能丟擲異常的語句,都可以用
- 丟擲異常
- 檢視
Integer.java
原始碼可知,丟擲異常的方法程式碼如下。public static int parseInt(String s, int radix) throws NumberFormatException { if (s == null) { throw new NumberFormatException("null"); } ... }
-
檢視
Integer.java
原始碼可知,丟擲異常的方法程式碼如下當發生錯誤時,例如,使用者輸入了非法的字元,我們就可以丟擲異常。如何丟擲異常?參考Integer.parseInt()
方法,丟擲異常分兩步:- 建立某個
Exception
的例項; - 用
throw
語句丟擲。void process2(String s) { if (s==null) { NullPointerException e = new NullPointerException(); throw e; } } 或: void process2(String s) { if (s==null) { throw new NullPointerException(); } }
- 在程式碼中獲取原始異常可以使用
Throwable.getCause()
方法。如果返回null
,說明已經是“根異常”了。 - 捕獲到異常並再次丟擲時,一定要留住原始異常,否則很難定位第一案發現場!
- 建立某個
- 在
try
或者catch
語句塊中丟擲異常,不會影響finally
的執行。JVM會先執行finally
,然後丟擲異常。 - 如果在執行
finally
語句時丟擲異常,那麼,finally
丟擲異常後,原來在catch
中準備丟擲的異常就“消失”了,因為只能丟擲一個異常。沒有被丟擲的異常稱為“被遮蔽”的異常(Suppressed Exception)。public class Main { public static void main(String[] args) { try { Integer.parseInt("abc"); } catch (Exception e) { System.out.println("catched"); throw new RuntimeException(e); } finally { System.out.println("finally"); throw new IllegalArgumentException(); } } } 輸出: catched finally Exception in thread "main" java.lang.IllegalArgumentException at Main.main(Main.java:11)
- 在極少數的情況下,我們需要獲知所有的異常。如何儲存所有的異常資訊?方法是先用
origin
變數儲存原始異常,然後呼叫Throwable.addSuppressed()
,把原始異常新增進來,最後在finally
丟擲。(通過Throwable.getSuppressed()
可以獲取所有的Suppressed Exception
。絕大多數情況下,在finally
中不要丟擲異常。因此,通常不需要關心Suppressed Exception
。)
1 public class Main { 2 public static void main(String[] args) throws Exception { 3 Exception origin = null; 4 try { 5 System.out.println(Integer.parseInt("abc")); 6 } catch (Exception e) { 7 origin = e; 8 throw e; 9 } finally { 10 Exception e = new IllegalArgumentException(); 11 if (origin != null) { 12 e.addSuppressed(origin); 13 } 14 throw e; 15 } 16 } 17 }
- 檢視
- 自定義異常
晚上接著更