第一章、異常概述與異常體系結構
在使用計算機語言進行項目開發的過程中,即使程序員把代碼寫得盡善盡美,在系統的運行過程中仍然會遇到一些問題,因為很多問題不是靠代碼能夠避免的,比如:客戶輸入數據的格式,讀取文件是否存在,網絡是否始終保持通暢等等。
1.1 異常
在Java語言中,將程序執行中發生的不正常情況稱為“異常”。(開發過程中的語法錯誤和邏輯錯誤不是異常)
Java程序在執行過程中所發生的異常事件可分為兩類:
Error
Java虛擬機無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。比如:StackOverflowError和OOM。一般不編寫針對性的代碼進行處理。
/* * Java虛擬機無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。 * 比如:StackOverflowError和OOM。 * 一般不編寫針對性的代碼進行處理。 * */ public class ErrorTest { public static void main(String[] args) { //1.棧溢出:java.lang.StackOverflowError // main(args); //2.堆溢出:java.lang.OutOfMemoryError // Integer[] arr = new Integer[1024*1024*1024]; } }
Exception
其它因編程錯誤或偶然的外在因素導致的一般性問題,可以使用針對性的代碼進行處理。例如:
- 空指針訪問
- 試圖讀取不存在的文件
- 網絡連接中斷
- 數組角標越界
對於這些錯誤,一般有兩種解決方法:
- 一是遇到錯誤就終止程序的運行。
- 另一種方法是由程序員在編寫程序時,就考慮到錯誤的檢測、錯誤消息的提示,以及錯誤的處理。
捕獲錯誤最理想的是在編譯期間,但有的錯誤只有在運行時才會發生。比如:除數為0,數組下標越界等
1.2 異常分類
運行時異常
- 是指編譯器不要求強制處置的異常。一般是指編程時的邏輯錯誤,是程序員應該積極避免其出現的異常。java.lang.RuntimeException類及它的子類都是運行時異常。
- 對於這類異常,可以不作處理,因為這類異常很普遍,若全處理可能會對程序的可讀性和運行效率產生影響。
編譯時異常
- 是指編譯器要求必須處置的異常。即程序在運行時由於外界因素造成的一般性異常。編譯器要求Java程序必須捕獲或聲明所有編譯時異常。
- 對於這類異常,如果程序不處理,可能會帶來意想不到的結果。
第二章、常見異常
* java.lang.Throwable
* |----java.lang.Error:一般不編寫針對性的代碼進行處理
* |----java.lang.Exception:可以進行異常處理
* |----編譯時異常(checked)
* |----IOEXception
* |----FileNotFoundException
* |----ClassNotFoundException
* |----運行時異常(unchecked)
* |----NullPointerException
* |----ArrayIndexOutOfBoundsException
* |----ClassCaseException
* |----NumberFormatException
* |----InputMismatchException
* |----ArithmaticException
import java.io.File; import java.io.FileInputStream; import java.util.Date; import java.util.Scanner; import org.junit.Test; public class ExceptionTest { // ******************以下是編譯時異常*************************** @Test public void test7() { // File file = new File("hello.txt"); // FileInputStream fis = new FileInputStream(file); // // int data = fis.read(); // while(data != -1){ // System.out.print((char)data); // data = fis.read(); // } // // fis.close(); } // ******************以下是運行時異常*************************** // ArithmeticException @Test public void test6() { int a = 10; int b = 0; System.out.println(a / b); } // InputMismatchException @Test public void test5() { Scanner scanner = new Scanner(System.in); int score = scanner.nextInt(); System.out.println(score); scanner.close(); } // NumberFormatException @Test public void test4() { String str = "123"; str = "abc"; int num = Integer.parseInt(str); } // ClassCaseException @Test public void test3() { Object obj = new Date(); String str = (String)obj; } // ArrayIndexOutOfBoundsException @Test public void test2() { // int[] arr = new int[10]; // System.out.println(arr[10]); // String str = "abc"; // System.out.println(str.charAt(3)); } // NullPointerException @Test public void test1() { // int[] arr = null; // System.out.println(arr[3]); // String str = "abc"; // str = null; // System.out.println(str.charAt(0)); } }
第三章、異常處理機制
在編寫程序時,經常要在可能出現錯誤的地方加上檢測的代碼,如進行x/y運算時,要檢測分母為0,數據為空,輸入的不是數據而是字符等。過多的if-else分支會導致程序的代碼加長、臃腫,可讀性差。因此采用異常處理機制。
- Java采用的異常處理機制,是將異常處理的程序代碼集中在一起,與正常的程序代碼分開,使得程序簡潔、優雅,並易於維護。
- Java提供的是異常處理的抓拋模型。
- Java程序的執行過程中如出現異常,會生成一個異常類對象,該異常對象將被提交給Java運行時系統,這個過程稱為拋出(throw)異常。
異常對象的生成
- 由虛擬機自動生成:程序運行過程中,虛擬機檢測到程序發生了問題,如果在當前代碼中沒有找到相應的處理程序,就會在后台自動創建一個對應異常類的實例對象並拋出——自動拋出
- 由開發人員手動創建:Exception exception = new ClassCastException();——創建好的異常對象不拋出對程序沒有任何影響,和創建一個普通對象一樣
方式一:try-catch-finally
方式二:throws + 異常類型
3.1 try-catch-finally
try
捕獲異常的第一步是用try{…}語句塊選定捕獲異常的范圍,將可能出現異常的代碼放在try語句塊中。
catch(Exceptiontypee)
在catch語句塊中是對異常對象進行處理的代碼。每個try語句塊可以伴隨一個或多個catch語句,用於處理可能產生的不同類型的異常對象。
如果明確知道產生的是何種異常,可以用該異常類作為catch的參數;也可以用其父類作為catch的參數。
比 如 : 可以用ArithmeticException 類作為參數的地 方 , 就可以用RuntimeException類作為參數,或者用所有異常的父類Exception類作為參數。
但不能是與ArithmeticException類無關的異常,如NullPointerException(catch中的語句將不會執行)。
- catch中的異常類型如果沒有子父類關系,則誰聲明在上,誰聲明在下無所謂。
- catch中的異常類型如果滿足子父類關系,則要求子類一定聲明在父類的上面。否則,報錯
捕獲異常的有關信息
與其它對象一樣,可以訪問一個異常對象的成員變量或調用它的方法。
- getMessage() 獲取異常信息,返回字符串
- printStackTrace() 獲取異常類名和異常信息,以及異常出現在程序中的位置。返回值void。
finally
- 捕獲異常的最后一步是通過finally語句為異常處理提供一個統一的出口,使得在控制流轉到程序的其它部分以前,能夠對程序的狀態作統一的管理。
- 不論在try代碼塊中是否發生了異常事件,catch語句是否執行,catch語句是否有異常,catch語句中是否有return,finally塊中的語句都會被執行。
- finally語句和catch語句是任選的、
- 像數據庫連接、輸入輸出流、網絡編程Socket等資源,JVM是不能自動的回收的,我們需要自己手動的進行資源的釋放。此時的資源釋放,就需要聲明在finally中。
體會1:使用try-catch-finally處理編譯時異常,是得程序在編譯時就不再報錯,但是運行時仍可能報錯。
相當於我們使用try-catch-finally將一個編譯時可能出現的異常,延遲到運行時出現。
體會2:開發中,由於運行時異常比較常見,所以我們通常就不針對運行時異常編寫try-catch-finally了。
針對於編譯時異常,我們說一定要考慮異常的處理。
public class FinallyTest { @Test public void test2() { FileInputStream fis = null; try { File file = new File("hello1.txt");//文件可能不存在,而出現異常 fis = new FileInputStream(file); int data = fis.read(); while (data != -1) { System.out.print((char) data); data = fis.read(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fis != null) fis.close(); } catch (IOException e) { e.printStackTrace(); } } } @Test public void testMethod() { int num = method(); System.out.println(num); } public int method() { try { int[] arr = new int[10]; System.out.println(arr[10]); return 1; } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); return 2; } finally { System.out.println("我一定會被執行"); return 3; } } @Test public void test1() { try { int a = 10; int b = 0; System.out.println(a / b); } catch (ArithmeticException e) { // e.printStackTrace(); int[] arr = new int[10]; System.out.println(arr[10]); } catch (Exception e) { e.printStackTrace(); } // System.out.println("我好慢呀~~~"); finally { System.out.println("我好慢呀~~~"); } } }
關於return 和 try-catch-finally結構
這里主要分為四種情況進行測試
第一種情況,try中有return,並且有finally
結論:try中的return返回的值會在try中代碼執行完畢后保存,先不返回,執行finally后再返回保存的值。
第二種情況,catch中有return
結論:與try中的執行順序一致。
第三種情況,try,catch,finally中都有return
結論:執行finally中的return。
第四種情況,try以及結構后都有return
結論:執行try中的return
public class Demo01 { public static void main(String[] args) { testDemo test = new testDemo(); System.out.println("return: i="+test.test01()); System.out.println("return: i="+test.test02()); System.out.println("return: i="+test.test03()); System.out.println("return: i="+test.test04()); } public static class testDemo{ /* 第一種情況 try中有return,並且有finally * */ public int test01(){ int i = 0; try{ i++; return i; }finally { i++; System.out.println("finally:i="+i); } } /* 第二種情況 catch中有return * */ public int test02(){ int i = 0; try{ i+=1; i=3/0; return i; }catch (Exception e){ i+=2; return i; }finally { i+=5; System.out.println("finally:i="+i); } } /* 第三種情況 try,catch,finally中都有return * */ public int test03(){ int i = 0; try{ i+=1; return i; }catch (Exception e){ i+=2; return i; }finally { i+=5; System.out.println("finally:i="+i); return i; } } /*第四種情況*/ public int test04(){ int i = 0; try{ i+=1; return i; }catch (Exception e){ i+=2; }finally { i+=5; System.out.println("finally:i="+i); } return i+=100; } } }
總的來說:
首先執行try中的代碼,如果有return,則保存住return的值后再執行finally的代碼,如果finally中有return,那么則執行finally的return,如果finally中沒有return,則執行完finally代碼后,會再執行try中的return,返回的值是之前在try中保存的值。catch的原理與try是一樣的。
3.2 throws
聲明拋出異常是Java中處理異常的第二種方式
- 如果一個方法(中的語句執行時)可能生成某種異常,但是並不能確定如何處理這種異常,則此方法應顯示地聲明拋出異常,表明該方法將不對這些異常進行處理,而由該方法的調用者負責處理。
- 在方法聲明中用throws語句可以聲明拋出異常的列表,throws后面的異常類型可以是方法中產生的異常類型,也可以是它的父類。
public class ExceptionTest2 { public static void main(String[] args){ try { method2(); } catch (IOException e) { e.printStackTrace(); } method3(); } public static void method3(){ try { method2(); } catch (IOException e) { e.printStackTrace(); } } public static void method2() throws IOException{ method1(); } public static void method1() throws FileNotFoundException,IOException{ File file = new File("hello1.txt"); FileInputStream fis = new FileInputStream(file); int data = fis.read(); while(data != -1){ System.out.print((char)data); data = fis.read(); } fis.close(); System.out.println("hahaha!"); } }
1. "throws + 異常類型"寫在方法的聲明處。指明此方法執行時,可能會拋出的異常類型。
一旦當方法體執行時,出現異常,仍會在異常代碼處生成一個異常類的對象,此對象滿足throws后異常類型時,就會被拋出。異常代碼后續的代碼,就不再執行!
關於異常對象的產生:
- 系統自動生成的異常對象
- 手動生成一個異常對象,並拋出(throw)
2. 體會:try-catch-finally:真正的將異常給處理掉了。
throws的方式只是將異常拋給了方法的調用者。 並沒有真正將異常處理掉。
重寫方法聲明拋出異常的原則
import java.io.FileNotFoundException; import java.io.IOException; /* * 方法重寫的規則之一: * 子類重寫的方法拋出的異常類型不大於父類被重寫的方法拋出的異常類型 * */ public class OverrideTest { public static void main(String[] args) { OverrideTest test = new OverrideTest(); test.display(new SubClass()); } public void display(SuperClass s){ try { s.method(); } catch (IOException e) { e.printStackTrace(); } } } class SuperClass{ public void method() throws IOException{ } } class SubClass extends SuperClass{ public void method()throws FileNotFoundException{ } }
開發中如何選擇使用try-catch-finally 還是使用throws?
- 如果父類中被重寫的方法沒有throws方式處理異常,則子類重寫的方法也不能使用throws,意味着如果子類重寫的方法中有異常,必須使用try-catch-finally方式處理。
- 執行的方法a中,先后又調用了另外的幾個方法,這幾個方法是遞進關系執行的。我們建議這幾個方法使用throws的方式進行處理。而執行的方法a可以考慮使用try-catch-finally方式進行處理。
3.3 手動拋出異常
Java異常類對象除在程序執行過程中出現異常時由系統自動生成並拋出,也可根據需要使用人工創建並拋出。
- 首先要生成異常類對象,然后通過throw語句實現拋出操作(提交給Java運行環境)
- 可以拋出的異常必須是Throwable或其子類的實例。下面的語句在編譯時將會產生語法錯誤:
public class StudentTest { public static void main(String[] args) { try { Student s = new Student(); // s.regist(1001); s.regist(-1001); System.out.println(s); } catch (Exception e) { // e.printStackTrace(); System.out.println(e.getMessage()); } } } class Student{ private int id; public void regist(int id) throws Exception{ if(id > 0){ this.id = id; }else{ // System.out.println("您輸入的數據非法!"); //手動拋出異常 // throw new RuntimeException("您輸入的數據非法!"); throw new Exception("您輸入的數據非法!"); } } @Override public String toString() { return "Student [id=" + id + "]"; } }
3.4 用戶自定義異常類
- 一般地,用戶自定義異常類都是RuntimeException的子類。
- 自定義異常類通常需要編寫幾個重載的構造器。
- 自定義異常需要提供serialVersionUID
- 自定義的異常通過throw拋出。
- 自定義異常最重要的是異常類的名字,當異常出現時,可以根據名字判斷異常類型。
/* * 如何自定義異常類? * 1.繼承於現有的異常結構:RuntimeException 、Exception * 2.提供全局常量:serialVersionUID * 3.提供重載的構造器 * */ public class MyException extends RuntimeException{ static final long serialVersionUID = -7034897193246939L; public MyException(){ } public MyException(String msg){ super(msg); } }
測試類:
package com.atguigu.java2; public class StudentTest { public static void main(String[] args) { try { Student s = new Student(); s.regist(-1001); System.out.println(s); } catch (Exception e) { // e.printStackTrace(); System.out.println(e.getMessage()); } } } class Student{ private int id; public void regist(int id) throws Exception { if(id > 0){ this.id = id; }else{ // System.out.println("您輸入的數據非法!"); //手動拋出異常對象 // throw new RuntimeException("您輸入的數據非法!"); // throw new Exception("您輸入的數據非法!"); throw new MyException("不能輸入負數"); //錯誤的 // throw new String("不能輸入負數"); } } @Override public String toString() { return "Student [id=" + id + "]"; } }
第四章、總結