傳智黑馬java基礎學習——day21(異常)

高數老師發表於2019-01-24

第21天 異常

今日內容介紹:

  1. 掌握異常概述
  2. 理解異常的基礎操作以及最簡單的捕獲處理
  3. 理解多異常捕獲處理
  4. 理解宣告丟擲異常
  5. 掌握自定義異常
  6. 掌握異常處理注意事項

 

  1. 異常

什麼是異常?Java程式碼在執行時期發生的問題就是異常。

在Java中,把異常資訊封裝成了一個類。當出現了問題時,就會建立異常類物件並丟擲異常相關的資訊(如異常出現的位置、原因等)。

    1. 異常的繼承體系

在Java中使用Exception類來描述異常。

檢視API中Exception的描述,Exception 類及其子類是 Throwable 的一種形式,它用來表示java程式中可能會產生的異常,並要求對產生的異常進行合理的異常處理。

 

繼續觀察,我們可以發現Exception有繼承關係,它的父類是Throwable。Throwable是Java 語言中所有錯誤或異常的超類,即祖宗類。

另外,在異常Exception類中,有一個子類要特殊說明一下,RuntimeException子類,RuntimeException及其它的子類只能在Java程式執行過程中出現。

 

我們再來觀察Throwable類,能夠發現與異常Exception平級的有一個Error,它是Throwable的子類,它用來表示java程式中可能會產生的嚴重錯誤。解決辦法只有一個,修改程式碼避免Error錯誤的產生。

 

異常繼承體系總結:

Throwable: 它是所有錯誤與異常的超類(祖宗類)

|- Error 錯誤

|- Exception 編譯期異常,進行編譯JAVA程式時出現的問題

|- RuntimeException 執行期異常, JAVA程式執行過程中出現的問題

    1. 異常與錯誤的區別

異常:指程式在編譯、執行期間發生了某種異常(XxxException),我們可以對異常進行具體的處理。若不處理異常,程式將會結束執行。

  1. 異常的產生演示如下:

public static void main(String[] args) {

int[] arr = new int[3];

System.out.println(arr[0]);

System.out.println(arr[3]);

// 該句執行時發生了陣列索引越界異常ArrayIndexOutOfBoundsException,由於沒有處理異常導致程式無法繼續執行程式結束。

System.out.println("over"); // 由於上面程式碼發生了異常,此句程式碼不會執行

}

 

錯誤:指程式在執行期間發生了某種錯誤(XxxError),Error錯誤通常沒有具體的處理方式,程式將會結束執行。Error錯誤的發生往往都是系統級別的問題,都是jvm所在系統發生的,並反饋給jvm的。我們無法針對處理,只能修正程式碼。

  1. 錯誤的產生演示如下:

public static void main(String[] args) {

int[] arr = new int[1024*1024*100];

//該句執行時發生了記憶體溢位錯誤OutOfMemoryError,開闢了過大的陣列空間,導致JVM在分配陣列空間時超出了JVM記憶體空間,直接發生錯誤。

}

    1. 異常的產生過程解析

先執行下面的程式,程式會產生一個陣列索引越界異常ArrayIndexOfBoundsException。我們通過圖解來解析下異常產生的過程。

  1. 工具類

class ArrayTools{

//對給定的陣列通過給定的角標獲取元素。

public static int getElement(int[] arr,int index) {

    int element = arr[index];

    return element;

}

}

  1. 測試類

class ExceptionDemo2 {

public static void main(String[] args) {

int[] arr = {34,12,67};

int num = ArrayTools.getElement(arr,4)

System.out.println("num="+num);

System.out.println("over");

}

}

 

  1. 上述程式執行過程圖解:

 

    1. 丟擲異常throw

在編寫程式時,我們必須要考慮程式出現問題的情況。比如,在定義方法時,方法需要接受引數。那麼,當呼叫方法使用接受到的引數時,首先需要先對引數資料進行合法的判斷,資料若不合法,就應該告訴呼叫者,傳遞合法的資料進來。這時需要使用丟擲異常的方式來告訴呼叫者。

在java中,提供了一個throw關鍵字,它用來丟擲一個指定的異常物件。那麼,丟擲一個異常具體如何操作呢?

  1. 1,建立一個異常物件。封裝一些提示資訊(資訊可以自己編寫)。
  2. 2,需要將這個異常物件告知給呼叫者。怎麼告知呢?怎麼將這個異常物件傳遞到呼叫者處呢?通過關鍵字throw就可以完成。throw 異常物件;

throw用在方法內,用來丟擲一個異常物件,將這個異常物件傳遞到呼叫者處,並結束當前方法的執行。

使用格式:

throw new 異常類名(引數);

例如:

throw new NullPointerException("要訪問的arr陣列不存在");

throw new ArrayIndexOutOfBoundsException("該索引在陣列中不存在,已超出範圍");

  1. 下面是異常類ArrayIndexOutOfBoundsException與NullPointerException的構造方法

學習完丟擲異常的格式後,我們通過下面程式演示下throw的使用。

  1. 編寫工具類,提供獲取陣列指定索引處的元素值

class ArrayTools{

//通過給定的陣列,返回給定的索引對應的元素值。

public static int getElement(int[] arr,int index) {

/*

若程式出了異常,JVM它會打包異常物件並丟擲。但是它所提供的資訊不夠給力。想要更清晰,需要自己丟擲異常資訊。

下面判斷條件如果滿足,當執行完throw丟擲異常物件後,方法已經無法繼續運算。這時就會結束當前方法的執行,並將異常告知給呼叫者。這時就需要通過異常來解決。

*/

if(arr==null){

throw new NullPointerException("arr指向的陣列不存在");

}

if(index<0 || index>=arr.length){

throw new ArrayIndexOutOfBoundsException("錯誤的角標,"+index+"索引在陣列中不存在");

}

int element = arr[index];

return element;

}

}

  1. 測試類

class ExceptionDemo3 {

public static void main(String[] args) {

int[] arr = {34,12,67}; //建立陣列

int num = ArrayTools.getElement(null,2);// 呼叫方法,獲取陣列中指定索引處元素

//int num = ArrayTools.getElement(arr,5);// 呼叫方法,獲取陣列中指定索引處元素

System.out.println("num="+num);//列印獲取到的元素值

}

}

 

    1. 宣告異常throws

宣告:將問題標識出來,報告給呼叫者。如果方法內通過throw丟擲了編譯時異常,而沒有捕獲處理(稍後講解該方式),那麼必須通過throws進行宣告,讓呼叫者去處理。

宣告異常格式:

修飾符 返回值型別 方法名(引數) throws 異常類名1,異常類名2 {   }

宣告異常的程式碼演示:

class Demo{

/*

如果定義功能時有問題發生需要報告給呼叫者。可以通過在方法上使用throws關鍵字進行宣告。

*/

public void show(int x)throws Exception {

if(x>0){

throw new Exception();

} else {

System.out.println("show run");

         }

}

}

throws用於進行異常類的宣告,若該方法可能有多種異常情況產生,那麼在throws後面可以寫多個異常類,用逗號隔開。

多個異常的情況,例如:

public static int getElement(int[] arr,int index) throws NullPointerException, ArrayIndexOutOfBoundsException {

if(arr==null){

throw new NullPointerException("arr指向的陣列不存在");

}

if(index<0 || index>=arr.length){

throw new ArrayIndexOutOfBoundsException("錯誤的角標,"+index+"索引在陣列中不存在");

}

int element = arr[index];

return element;

}

    1. 捕獲異常trycatchfinally

捕獲:Java中對異常有針對性的語句進行捕獲,可以對出現的異常進行指定方式的處理

捕獲異常格式:

try {

//需要被檢測的語句。

}

catch(異常類 變數) { //引數。

//異常的處理語句。

}

finally {

//一定會被執行的語句。

}

try:該程式碼塊中編寫可能產生異常的程式碼。

catch:用來進行某種異常的捕獲,實現對捕獲到的異常進行處理。

finally:有一些特定的程式碼無論異常是否發生,都需要執行。另外,因為異常會引發程式跳轉,導致有些語句執行不到。而finally就是解決這個問題的,在finally程式碼塊中存放的程式碼都是一定會被執行的。

演示如下:

class ExceptionDemo{

public static void main(String[] args){ //throws ArrayIndexOutOfBoundsException

try {

              int[] arr = new int[3];

System.out.println( arr[5] );// 會丟擲ArrayIndexOutOfBoundsException

當產生異常時,必須有處理方式。要麼捕獲,要麼宣告。

}

catch (ArrayIndexOutOfBoundsException e) { //括號中需要定義什麼呢?try中丟擲的是什麼異常,在括號中就定義什麼異常型別。

System.out.println("異常發生了");

} finally {

              arr = null; //把陣列指向null,通過垃圾回收器,進行記憶體垃圾的清除

}

System.out.println("程式執行結果");

}

}

    1. try…catch…finally異常處理的組合方式
  1. try catch finally組合:檢測異常,並傳遞給catch處理,並在finally中進行資源釋放。
  2. try catch組合 : 對程式碼進行異常檢測,並對檢測的異常傳遞給catch處理。對異常進行捕獲處理。

void show(){ //不用throws

try{

throw new Exception();//產生異常,直接捕獲處理

}catch(Exception e){

//處理方式

}

}

  1. 一個try 多個catch組合 : 對程式碼進行異常檢測,並對檢測的異常傳遞給catch處理。對每種異常資訊進行不同的捕獲處理。

void show(){ //不用throws

try{

throw new Exception();//產生異常,直接捕獲處理

}catch(XxxException e){

//處理方式

}catch(YyyException e){

//處理方式

}catch(ZzzException e){

//處理方式

}

}

注意:這種異常處理方式,要求多個catch中的異常不能相同,並且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。

 

  1. try finally 組合: 對程式碼進行異常檢測,檢測到異常後因為沒有catch,所以一樣會被預設jvm丟擲。異常是沒有捕獲處理的。但是功能所開啟資源需要進行關閉,所有finally。只為關閉資源。

void show(){//需要throws

try{

    throw new Exception();

}finally {

//釋放資源

}

}

 

    1. 執行時期異常
  1. RuntimeException和他的所有子類異常,都屬於執行時期異常。NullPointerException,ArrayIndexOutOfBoundsException等都屬於執行時期異常.
  2. 執行時期異常的特點:
  3. 方法中丟擲執行時期異常,方法定義中無需throws宣告,呼叫者也無需處理此異常
  4. 執行時期異常一旦發生,需要程式人員修改原始碼.

class ExceptionDemo{

    public static void main(String[] args){

         method();

    }

    public static void method(){

        throw new RuntimeException();

    }

}

    1. 異常在方法重寫中細節
  1. 子類覆蓋父類方法時,如果父類的方法宣告異常,子類只能宣告父類異常或者該異常的子類,或者不宣告。

例如:

class Fu {

public void method () throws RuntimeException {

}

}

class Zi extends Fu {

public void method() throws RuntimeException { }  //丟擲父類一樣的異常

//public void method() throws NullPointerException{ } //丟擲父類子異常

}

 

  1. 當父類方法宣告多個異常時,子類覆蓋時只能宣告多個異常的子集。

例如:

class Fu {

public void method () throws NullPointerException, ClassCastException{

}

}

class Zi extends Fu {

public void method()throws NullPointerException, ClassCastException { }   public void method() throws NullPointerException{ } //丟擲父類異常中的一部分

public void method() throws ClassCastException { } //丟擲父類異常中的一部分

}

3、當被覆蓋的方法沒有異常宣告時,子類覆蓋時無法宣告異常的。

例如:

class Fu {

public void method (){

}

}

class Zi extends Fu {

public void method() throws Exception { }//錯誤的方式

}

舉例:父類中會存在下列這種情況,介面也有這種情況

問題:介面中沒有宣告異常,而實現的子類覆蓋方法時發生了異常,怎麼辦?

答:無法進行throws宣告,只能catch的捕獲。萬一問題處理不了呢?catch中繼續throw丟擲,但是隻能將異常轉換成RuntimeException子類丟擲。

interface Inter {

public abstract void method();

}

class Zi implements Inter {

public void method(){ //無法宣告 throws Exception

int[] arr = null;

if (arr == null) {

//只能捕獲處理

try{

throw new Exception(“哥們,你定義的陣列arr是空的!”);

} catch(Exception e){

    System.out.println(“父方法中沒有異常丟擲,子類中不能丟擲Exception異常”);

    //我們把異常物件e,採用RuntimeException異常方式丟擲

throw new RuntimeException(e);

}

}

}

}

    1. 異常中常用方法

在Throwable類中為我們提供了很多操作異常物件的方法,常用的如下:

  1. getMessage方法:返回該異常的詳細資訊字串,即異常提示資訊
  2. toString方法:返回該異常的名稱與詳細資訊字串
  3. printStackTrace:在控制檯輸出該異常的名稱與詳細資訊字串、異常出現的程式碼位置

異常的常用方法程式碼演示:

try {

Person p= null;

if (p==null) {

throw new NullPointerException(“出現空指標異常了,請檢查物件是否為null”);

}

} catch (NullPointerException e) {

String message = e.getMesage();

System.out.println(message );

 

String result = e.toString();

System.out.println(result);

 

e.printStackTrace();

}

  1. 自定義異常

在上述程式碼中,發現這些異常都是JDK內部定義好的,並且這些異常不好找。書寫時也很不方便,那麼能不能自己定義異常呢?

之前的幾個異常都是java通過類進行的描述。並將問題封裝成物件,異常就是將問題封裝成了物件。這些異常不好認,書寫也很不方便,能不能定義一個符合我的程式要求的異常名稱。既然JDK中是使用類在描述異常資訊,那麼我們也可以模擬Java的這種機制,我們自己定義異常的資訊,異常的名字,讓異常更符合自己程式的閱讀。準確對自己所需要的異常進行類的描述。

    1. 自定義異常類的定義

通過閱讀異常原始碼:發現java中所有的異常類,都是繼承Throwable,或者繼承Throwable的子類。這樣該異常才可以被throw丟擲。

說明這個異常體系具備一個特有的特性:可拋性:即可以被throw關鍵字操作。

並且查閱異常子類原始碼,發現每個異常中都呼叫了父類的構造方法,把異常描述資訊傳遞給了父類,讓父類幫我們進行異常資訊的封裝。

例如NullPointerException異常類原始碼:

public class NullPointerException extends RuntimeException {

    public NullPointerException() {

        super();//呼叫父類構造方法

    }

    public NullPointerException(String s) {

        super(s);//呼叫父類具有異常資訊的構造方法

    }

}

現在,我們來定義個自己的異常,即自定義異常。

格式:

Class 異常名 extends Exception{ //或繼承RuntimeException

public 異常名(){

}

public 異常名(String s){

super(s);

}

}

 

  1. 自定義異常繼承Exception演示

class MyException extends Exception{

/*

為什麼要定義建構函式,因為看到Java中的異常描述類中有提供對異常物件的初始化方法。

*/

public MyException(){

super();

}

public MyException(String message) {

super(message);// 如果自定義異常需要異常資訊,可以通過呼叫父類的帶有字串引數的建構函式即可。

}

}

 

  1. 自定義異常繼承RuntimeException演示

class MyException extends RuntimeException{

/*

為什麼要定義建構函式,因為看到Java中的異常描述類中有提供對異常物件的初始化方法。

*/

MyException(){

super();

}

MyException(String message) {

super(message);// 如果自定義異常需要異常資訊,可以通過呼叫父類的帶有字串引數的建構函式即可。

}

}

 

    1. 自定義異常的練習

定義Person類,包含name與age兩個成員變數。

在Person類的有引數構造方法中,進行年齡範圍的判斷,若年齡為負數或大於200歲,則丟擲NoAgeException異常,異常提示資訊“年齡數值非法”。

要求:在測試類中,呼叫有引數構造方法,完成Person物件建立,並進行異常的處理。

  1. 自定義異常類

class NoAgeException extends Exception{

NoAgeException() {

super();

}

 

NoAgeException(String message) {

super(message);

}

}

  1. Person類

class Person{

private String name;

private int age;

Person(String name,int age) throws NoAgeException {

//加入邏輯判斷。

if(age<0 || age>200) {

throw new NoAgeException(age+",年齡數值非法");

}

this.name = name;

this.age = age;

}

//定義Person物件對應的字串表現形式。覆蓋Object中的toString方法。

public String toString() {

return "Person[name="+name+",age="+age+"]";

}

}

  1. 測試類

class ExceptionDemo{

public static void main(String[] args) {

try {

Person p = new Person("xiaoming",20);

System.out.println(p);

}

catch (NoAgeException ex){

System.out.println("年齡異常啦");

}

System.out.println("over");

}

}

 

總結一下,建構函式到底丟擲這個NoAgeException是繼承Exception呢?還是繼承RuntimeException呢?

  1. 繼承Exception,必須要throws宣告,一宣告就告知呼叫者進行捕獲,一旦問題處理了呼叫者的程式會繼續執行。
  2. 繼承RuntimeExcpetion,不需要throws宣告的,這時呼叫是不需要編寫捕獲程式碼的,因為呼叫根本就不知道有問題。一旦發生NoAgeException,呼叫者程式會停掉,並有jvm將資訊顯示到螢幕,讓呼叫者看到問題,修正程式碼。
  1. 總結
    1. 知識點總結
  1. 異常:就是程式中出現的不正常的現象(錯誤與異常)
  2. 異常的繼承體系:

Throwable: 它是所有錯誤與異常的超類(祖宗類)

|- Error 錯誤,修改java原始碼

|- Exception 編譯期異常, javac.exe進行編譯的時候報錯

|- RuntimeException 執行期異常, java出現執行過程中出現的問題

  1. 異常處理的兩種方式:
  2. 1,出現問題,自己解決 try…catch…finally

try{

可能出現異常的程式碼

} catch(異常類名  物件名){

    異常處理程式碼

} finally {

異常操作中一定要執行的程式碼

}

  1. 2,出現問題,別人解決 throws

格式:

修飾符 返回值型別 方法名(引數) throws 異常類名1,異常類名2,...{}

public void method() throws Exception{}

 

  1. 異常分類

異常的根類是Throwable,其下有兩個子類:Error與Exception,平常所說的異常指Exception。

  1. 嚴重錯誤Error,無法通過處理的錯誤
  2. 編譯時異常Exception,編譯時無法編譯通過。如日期格式化異常
  3. 執行時異常RuntimeException,是Exception的子類,執行時可能會報錯,可以不處理。如空指標異常

 

  1. 異常基本操作
  1. 建立異常物件
  2. 丟擲異常
  3. 處理異常
  1. 捕獲處理,將異常獲取,使用try/catch做分支處理

  try{

需要檢測的異常;

}  catch(異常物件) {

通常我們只使用一個方法:printStackTrace列印異常資訊

}

  1. 宣告丟擲處理,出現異常後不處理,宣告丟擲給呼叫者處理。

   方法宣告上加throws  異常類名

  1. 注意:異常的處理,指處理異常的一種可能性,即有了異常處理的程式碼,不一定會產生異常。如果沒有產生異常,則程式碼正常執行,如果產生了異常,則中斷當前執行程式碼,執行異常處理程式碼。

 

  1. 異常注意事項
  1. 多異常處理

捕獲處理:

1多個異常可以分別處理

2多個異常一次捕獲多次處理

3多個異常一次捕獲,採用同一種方式處理

宣告丟擲異常:

宣告上使用,一次宣告多個異常

 

  1. 執行時異常被丟擲可以不處理。即不捕獲也不宣告丟擲
  2. 如果父類丟擲了多個異常,子類覆蓋父類方法時,只能丟擲相同的異常或者是他的子集
  3. 父類方法沒有丟擲異常,子類覆蓋父類該方法時也不可丟擲異常。此時子類產生該異常,只能捕獲處理,不能宣告丟擲
  4. 當多異常處理時,捕獲處理,前邊的類不能是後邊類的父類

 

  1. 自定義異常

如果Java沒有提供你需要的異常,則可以自定義異常類。

定義方法:編譯時異常繼承Exception,執行時異常繼承RuntimeException。

相關文章