0八、異常處理


前言

      去年四月份大一下半學期正式開始學習Java,一路從java基礎、數據庫、jdbc、javaweb、ssm以及Springboot,其中也學習了一段時間數據結構。html

      在javaweb期間作了圖書商城項目、ssm階段作了權限管理項目,springboot學了以後手癢去b站看視頻作了個我的博客項目(已部署到服務器,正在備案中)。期間也不斷進行作筆記,總結,可是越學到後面越感受有點虛,以爲本身基礎還有欠缺。java

      以後一段時間我會從新回顧java基礎、學習一些設計模式,學習多線程併發之類,以及接觸一些jvm的相關知識,越學到後面越會感受到基礎的重要性,以後也會以博客形式輸出學習的內容。web

      如今整理的java知識基礎點是在以前學習尚硅谷java課程的筆記基礎之上加工彙總,部分圖片會引用尚硅谷或網絡上搜集或本身畫,在從新回顧的過程當中也在不斷進行查漏補缺,儘量將以前困惑的點都解決,讓本身更上一層樓吧。spring

      博客目錄索引博客目錄索引(持續更新)數據庫



1、異常概述與異常體系結構

概述說明編程

異常分類:編譯時異常與運行時異常設計模式

  • 編譯時異常:源代碼.java文件在編譯器編譯時發生的錯誤。
  • 運行時異常:執行字節碼文件時發生不正確狀況,其稱爲異常。

這裏要講的異常指的是運行時異常,其分爲兩類異常事件數組

  • Error:Java虛擬機沒法解決的嚴重問題。例如:JVM系統內部錯誤、資源耗盡等嚴重狀況。好比:StackOverflowError(棧溢出)和OOM(內存溢出)。
  • Exception:其餘因編程錯誤或偶然的外在因素致使的通常性問題,可使用針對性的代碼進行處理,例如空指針訪問、試圖讀取不存在文件、網絡鏈接中斷、數組角標越界問題。

Error表示不可控異常通常不編寫針對性的代碼進行處理;而對於Exception這類異常,咱們能夠編寫針對性的代碼進行處理。springboot


Error的實例服務器

看Error的兩個實例:棧溢出與內存溢出

package com.mjj.pro7;

public class Test1 {
    public static void main(String[] args) {
        //1.棧溢出,無限壓棧 報錯 java.lang.StackOverflowError
        //main(args);
        //2.堆溢出,建立佔用超過初始jvm使用的內存,java.lang.OutOfMemoryError:
        Integer[] arr = new Integer[1024*1024*1024];
    }
}

針對於這Error的狀況,咱們不對其進行鍼對性處理。



2、常見異常

異常體系結構

非受檢異常:例如RuntimeException在默認狀況下會獲得自動處理,能夠捕獲RuntimeException異常,但對於本身的封裝RuntimeException的異常,一部分仍是須要進行手動拋出。

受檢異常:Java編譯器要求程序必須捕獲或聲明拋出這種異常。



RuntimeException舉例

表示運行時異常,接下來進行實例舉例

NullPointerException(空指針)

import org.junit.Test;

public class ExceptionTest {

    //NullPointerException
    @Test
    public void test1(){
        //例1
//      int[] arr = null;
//      System.out.println(arr[3]);
        
        //例2
        String str = null;
        System.out.println(str.charAt(0));
    }
}


IndexOutOfBoundsException(下標越界)

//IndexOutOfBoundsException
    @Test
    public void test2(){
        //第一種 ArraryIndexOutOfBoundsException
//      int[] arr = new int[10];
//      System.out.println(arr[10]);
        
        //第二種 StringIndexOutOfBoundsException
        String arr = "123";
        System.out.println(arr.charAt(3));
    }


ClassCastException(類型轉換)

//ClassCastException 類型轉換問題
@Test
public void test3(){
    Object obj = new Date();
    String str = (String)obj;
}


NumberFormatException(數值轉換)

//NumberFormatException 數值類型轉換
@Test
public void test4(){
    String str = "123";  //是經過的
    String str1 = "abc";
    int num = Integer.parseInt(str1);
}


InputMismatchException(輸入不匹配)

//InputMismatchException 輸入不匹配
@Test
public void test5(){
    Scanner sca = new Scanner(System.in);
    int num = sca.nextInt();  //當輸入abc時會報這個錯誤
    sca.close();
}


ArithmeticException(算術異常)

//ArithmeticException 算術異常
@Test
public void test6(){
    System.out.println(5/0);//java.lang.ArithmeticException: / by zero
}


3、異常處理概述

異常處理好處

:對於上面異常體系結構中不受檢異常指的是咱們不進行異常處理系統也會自行捕捉到異常,而且輸出異常信息。那麼咱們處理異常與不處理異常的區別在哪以及爲何要進行異常處理?

  • 區別描述:對不受檢異常不進行異常處理時,若咱們程序發生異常,就會直接終止程序;如果進行異常處理,程序會按照咱們要求進行異常處理而且繼續執行程序。
  • 目的:可以讓咱們對異常更好的處理以及程序繼續執行下去。

看一下是否進行異常處理的區別

①不進行異常處理

public class Main {
    public static void main(String[] args){
        String str = "abc";
        int number;
        //有異常的語句
        int i = Integer.parseInt(str);
        System.out.println(123);
    }
}

image-20210128195644491

能夠看到一旦出現異常程序直接中止,後面的語句再也不執行!!!

②進行異常處理

public class Main {
    public static void main(String[] args){
        String str = "abc";
        int number;
        //進行異常處理
        try {
            int i = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        System.out.println(123);
    }
}

image-20210128195826541

咱們能夠看到程序完整的執行了下來後面的語句也進行了執行,這裏咱們是對異常狀況進行打印輸出!!!



抓拋模型

對異常的處理會有兩個過程:拋、抓

  • 過程一:"拋",程序在正常執行的過程當中,一旦出現異常,就會在異常代碼處生成一個對應異常類的對象,並將此對象拋出。一旦拋出對象之後,其後的代碼都不執行。
  • 過程二:"抓",能夠理解爲對異常的處理方式,如:try-catch-finallythrows


異常處理機制一:try-catch-finally

語法

try{
//可能出現異常的代碼
}catch(異常類型1 變量名1){
//處理異常的方式1
}catch(異常類型2 變量名2){
//處理異常的方式2
}
...
finally{
//必定會執行的代碼
}

try-catch注意點

  1. try中包裹可能出現異常的代碼,若出現異常先生成指定異常,接着去catch中去找匹配異常。
  2. 當try中出現錯誤,進入catch部分中進行異常處理,一旦處理完就執行finally中包裹內容(finally存在),接着跳出try-catch結構,繼續執行try-catch結構以外的代碼。
  3. catch中的異常類型若是沒有子父類關係,誰在上誰在下都無所謂;如果多個catch中有子父類關係的,子類必需要聲明在父類異常之上,不然報錯。父類在上的話,子類異常聲明在下就沒意義了!!!
  4. catch的{}中異常對象處理方式常見的兩種:
    • e.getMessage():獲取異常的簡略信息。
    • e.printStackTrace():比較經常使用,打印完整的堆棧狀況,會顯示指定的出錯位置。
  5. 注意在try中聲明的變量(局部變量),其生命週期只在try結構中,try-catch結構以外沒法調用。

注意點5中的實例演示:

public class Main {
    public static void main(String[] args){
        String str = "abc";
        int number;
        try {
            int i = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            //e.printStackTrace(); //詳細堆棧錯誤信息
            System.out.println(e.getMessage());//簡略信息
        }
        
    }
}

image-20210128194507363

image-20210128194559185



finally注意點

  1. finally中聲明的代碼是必定執行的,儘管catch中出現異常會先執行finally,再拋出異常。
  2. try-catch-finally使用於方法中catch與finally包含return,不管catch是否有異常,都會執行finally中的return返回

注意點1中狀況舉例

public class Main {
    public static void main(String[] args){
        try{
            int a=10;
            int b=0;
            System.out.println(a/b);
        }catch(ArithmeticException e){
            int[] a = new int[10];
            //這裏有數組越界異常
            System.out.println(a[10]);
        }
      finally{
          System.out.println("執行finally語句");
      }
        System.out.println("長路哥哥");
    }
}
  • catch中若是出現異常,只會執行finally中的代碼,try-catch結構外的也不會處理!!!

image-20210128200901666


注意點2中舉例

public class Main {
    public static void main(String[] args){
        System.out.println(Main.method());
        System.out.println(123456);
    }

    public static int method(){
        try{
            System.out.println(10/0);
        }catch(ArithmeticException e){
            int[] a = new int[10];
            //這裏有數組越界異常
            System.out.println(a[10]);
            return 2;
        }
        finally{
            return 3;
        }
    }
}

image-20210128202359796

能夠看到結果沒有出現異常,說明返回的是finally中的。

緣由:在方法中catch出現異常了,會直接先去執行finally中內容,這裏finally中是返回值,那麼當catch異常處理前方法已經結束了,因此沒有報異常出來!!!



finally實際使用

例如數據庫鏈接、輸入輸出流,網絡編程Socket等資源,JVM是不能自動的回收的,咱們須要本身手動的進行資源的釋放,此時的資源釋放,就須要聲明在finally中。

下面例子是演示輸入輸出流的關閉

import java.io.*;

public class Main {
    public static void main(String[] args){
        File file = new File("hello.txt");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            int read = fis.read();
            while(read != -1){
                System.out.println((char)read);
                read = fis.read();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //fis放在這裏進行關閉資源 再使用一個try-catch是由於IOException是受檢型的必須聲明
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

在項目工程目錄下添加hello.txt文件後執行

image-20210128212349704



總結

使用於main()中
try-catch:try異常,catch無異常進行處理,執行try-catch結構外的。
try-catch:try異常,catch異常,直接結束程序(打印異常)。
try-catch-finally:try異常,執行指定catch中,無異常會先執行完catch內容,接着執行finally中內容,而後執行try-catch之外內容。
try-catch-finally:try異常,執行指定catch中如果有異常會先執行finally內容,接着程序結束(打印異常)。

調用單獨方法中
try-catch:try異常,catch無異常,正常執行後序程序。
try-catch:try異常,catch異常,程序直接結束(打印異常)。
try-catch-finally:catch與finally中都有返回值,如果catch中出現異常,會先去找finally,以後直接結束方法,此時異常也不會打印。
無try-catch結構捕捉異常,也無throws拋出,對於出現非受檢查的異常系統會自動拋出。

總結總結:catch中無異常,執行完catch後執行finally以及try-catch結構以外的;catch中如果出現異常會先去執行finally中內容,接着程序直接中止。



異常處理機制二:throws

認識及使用throws

語法:throws 異常類

//例如
public void method()throws RuntimeException{
    
}

寫在方法的聲明處,指明此方法執行時可能會拋出的異常。一旦方法體執行時,出現異常,仍會在異常代碼處生成一個異常類的對象,若此對象知足throws的異常就會拋出,在方法中出現異常代碼後續的代碼就不會執行。

與try-catch-finally比較

  • try-catch-finally:真正將異常處理掉。
  • throws:將異常拋給方法的調用者,並無真正將異常處理掉。

實例1:throws聲明可能會拋出的異常(表示了一種規範),方法中出現異常則會向上傳遞:

import java.io.*;

public class Main {
    public static void main(String[] args){
        try {
            method1();
        } catch (RuntimeException e) {
            System.out.println("捕捉到方法中的異常");
        }
    }

    public static void method1() throws RuntimeException {
        method2();
    }

    public static void method2 () throws RuntimeException{
        System.out.println(5/0);
    }
}

image-20210128223719226

其實就算我兩個方法都不添加throws RuntimeException,最終使用try-catch也是可以接收到的,那爲何要使用thorows聲明勒?也能夠算是一種規範吧,聲明則指明其方法可能會出現的異常,好讓調用方法者知道捕捉異常時使用什麼類型捕捉!!!不聲明的話使用try-catch結構中catch默認會是Exception。



重寫方法異常拋出規則

對於重寫方法throws的規則

  • 子類重寫方法的拋出異常類型應當小於或等於父類方法拋出異常類型
  • 父類中沒有throws異常,子類重寫時也不該該有

實例以下:

class SuperClass{

    public void method()throws IOException{

    }
}

class SubClass extends SuperClass{   //繼承SuperClass
    @Override
    public void method() throws FileNotFoundException {  //子類重寫方法異常應當小於等於父類的異常

    }
}


開發中如何選擇異常處理機制

  1. 如果被重寫父類的方法中沒有throws,那麼子類重寫的方法一定也沒有throws,若重寫方法有異常,必須使用try-catch解決。
  2. 若調用多個不一樣的方法,而且幾個方法是遞進關係,那麼建議使用throws,最早執行調用的方法使用try-catch捕捉異常。

try(){}語法

Java7 build 105版開始,Java7的編譯器和運行環境支持新的 try-with-resources 語句,稱爲 ARM 塊(Automatic Resource Management) ,自動資源管理。

語法如:try塊退出時,會自動調用res.close()方法,關閉資源

try(Resource res = xxx)//可指定多個資源
{
     work with res
}

這相比咱們以前在finally中一個個手動關閉資源好的多。

咱們看一下二者對比

//之前try{}catch{}:
FileInputStream fis = null;
FileOutputStream fos = null;
try {
    //目標圖片1234.jpg
    fis = new FileInputStream(new File("1234.jpg"));
    //複製地址
    fos = new FileOutputStream(new File("圖片.jpg"));
    ....
} catch (IOException e) {
    e.printStackTrace();
}finally {
    if(fis != null){
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if(fos != null){
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//如今的try(){}
try (
    FileInputStream fis = new FileInputStream(new File("1234.jpg"));
    FileOutputStream fos = new FileOutputStream(new File("圖片.jpg"));
	){
    ....
} catch (IOException e) {
    e.printStackTrace();
}

省去了大量的語句,舒服!!!



4、手動拋出異常throw

介紹一下異常對象的產生:①系統在出現異常時自動生成異常對象②手動生成一個異常對象,例如throw new Exception()

只要是使用了throw,就必定表示拋出了一個異常,而throws只是用於聲明可能拋出的異常便於調用其方法的人作出相應的異常處理!!

實例:手動拋出異常,並使用throws聲明該方法可能會有什麼異常

public class Main {
    public static void main(String[] args){
        try {
            Main.method("");
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void method(String str)throws RuntimeException{
        if(!"".equals(str)){
            System.out.println(str);
        }else {
            throw new RuntimeException("str不能爲空");
        }
    }

}

image-20210128233116965



5、自定義異常類

首先問一下爲何要自定義異常類勒?有幾點緣由,例如設置多個本身定義的異常類,僅僅捕獲其本身所關心的異常類,並知道如何處理;根據本身的需求出現異常的時候來去作特殊的處理;異常分類,業務中不一樣場景拋出不一樣異常,便於統一捕獲並根據類型作進一步處理

對於自定義異常類有幾點規範以下

  • 繼承於現有的異常類如:Exception、RuntimeException....
  • 自定義異常類須要提供一個全局常量如:serialVersionUID ,用來標識本身定義的異常類
  • 必須提供重載的構造器

自定義異常:包含最基本的幾個部分

class MyException extends RuntimeException{
    //須要一個UID來表示本身的自定義異常類
    static final long serialVersionUID = -7034897190745766959L;

    public MyException(){
    }

    //想要有自定義異常描述,就須要有一個有參構造
    public MyException(String msg){
        super(msg);
    }
}
//測試上面的自定義異常
public class Main {
    public static void main(String[] args){
        try {
            Main.method("");
        } catch (MyException e) {
            System.out.println(e.getMessage());
        }
    }

	//測試使用
    public static void method(String str)throws MyException{
        if(!"".equals(str)){
            System.out.println(str);
        }else {
            throw new MyException("自定義異常描述:str不許爲空");
        }
    }

}

image-20210128235408009



參考資料

[1]. Java受檢異常和非受檢異常

[2]. Java基礎之《受檢查異常和不受檢查異常》

[3]. Java 中的異常和處理詳解

[4]. Java中關鍵字throw和throws的區別

[5]. Java:爲何要有自定義異常?

[6]. Java自定義異常(優雅的處理異常) 實際應用配合枚舉

[7]. java try(){}catch(){}自動資源釋放



我是長路,感謝你的閱讀,若有問題請指出,我會聽取建議並進行修正。 歡迎關注個人公衆號:長路Java,其中會包含軟件安裝等其餘一些資料,包含一些視頻教程以及學習路徑分享。 學習討論qq羣:891507813 咱們能夠一塊兒探討學習 註明:轉載可,須要附帶上文章連接

相關文章
相關標籤/搜索