主頁 > 後端開發 > Java——你真的了解Java例外處理機制嗎?

Java——你真的了解Java例外處理機制嗎?

2022-01-04 09:49:07 後端開發

目錄

1.初識例外

2.例外的基本用法

例外處理流程

3.為什么要使用例外?

例外應只用于例外的情況

4. 例外的種類

4.1 受查例外

解決方案:

4.2非受查例外

5.如何使用例外

避免不必要的使用受查例外

6.自定義例外


1.初識例外

我們在寫代碼的時候都或多或少碰到了大大小小的例外,例如:

public class Test {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        System.out.println(arr[5]);
    }
}

當我們陣列越界時,編譯器會給我們報陣列越界,并提示哪行出了錯,

再比如:

class Test{    
    int num = 10;
    public static void main(String[] args) {
        Test test = null;
        System.out.println(test.num);
    }
}

當我們嘗試用使用空物件時,編譯器也會報空指標例外:

那么究竟什么是例外?

所謂例外指的就是程式在 運行時 出現錯誤時通知呼叫者的一種機制 .

關鍵字 "運行時" ,有些錯誤是這樣的, 例如將 System.out.println 拼寫錯了, 寫成了

system.out.println. 此時編譯程序中就會出 , 這是 "編譯期" 出錯.

而運行時指的是程式已經編譯通過得到 class 檔案了 , 再由 JVM 執行程序中出現的錯誤 .

2.例外的基本用法

Java例外處理依賴于5個關鍵字:try、catch、finally、throws、throw,下面來逐一介紹下,

①try:try塊中主要放置可能會產生例外的代碼塊,如果執行try塊里的業務邏輯代碼時出現異

常,系統會自動生成一個例外物件,該例外物件被提交給運行環境,這個程序被稱為拋出

(throw)例外,Java環境收到例外物件時,會尋找合適的catch塊(在本方法或是呼叫方

法),

②catch: catch 代碼塊中放的是出現例外后的處理行為,也可以寫此例外出錯的原因或者打

堆疊上的錯誤資訊,但catch陳述句不能為空,因為一旦將catch陳述句寫為空,就代表忽略了此

常,如:

空的catch塊會使例外達不到應有的目的,即強迫你處理例外的情況,忽略例外就如同忽略

火警信號一樣——若把火警信號關掉了,當真正的火災發生時,就沒有人能看到火警信號

了,或許你會僥幸逃過一劫,或許結果將是災難性的,每當見到空的catch塊時,我們都應該

警鐘長鳴,

當然也有一種情況可以忽略例外,即關閉fileinputstream(讀寫本地檔案)的時候,因為你還

沒有改變檔案的狀態,因此不必執行任何恢復動作,并且已經從檔案中讀取到所需要的信

息,因此不必終止正在進行的操作,

③finally:finally 代碼塊中的代碼用于處理善后作業, 會在最后執行,也一定會被執行,當遇

到try或catch中return或throw之類可以終止當前方法的代碼時,jvm會先去執行finally中的語

句,當finally中的陳述句執行完畢后才會回傳來執行try/catch中的return,throw陳述句,如果

finally中有return或throw,那么將執行這些陳述句,不會在執行try/catch中的return或throw語

句,finally塊中一般寫的是關閉資源之類的代碼,但是我們一般不在finally陳述句中加入return

陳述句,因為他會覆寫掉try中執行的return陳述句,例如:

finally將最后try執行的return 10覆寫了,最后結果回傳了20.

④throws:在方法的簽名中,用于拋出此方法中的例外給呼叫者,呼叫者可以選擇捕獲或者

拋出,如果所有方法(包括main)都選擇拋出(或者沒有合適的處理例外的方式,即例外類

型不匹配)那么最終將會拋給JVM,就會像我們之前沒使用try、catch陳述句一樣,JVM列印出

堆疊軌跡(例外鏈),

⑤throw:用于拋出一個具體的例外物件,常用于自定義例外類中,

ps:

關于 "呼叫堆疊",方法之間是存在相互呼叫關系的, 這種呼叫關系我們可以用 "呼叫堆疊" 來描述.

JVM 中有一塊記憶體空間稱為 "虛擬機堆疊" 專門存盤方法之間的呼叫關系. 當代碼中出現例外

的時候, 我們就可以使用 e.printStackTrace() 的方式查看出現例外代碼的呼叫堆疊,一般寫在catch陳述句中,

例外處理流程

程式先執行 try 中的代碼

如果 try 中的代碼出現例外, 就會結束 try 中的代碼, 看和 catch 中的例外型別是否匹配.

如果找到匹配的例外型別, 就會執行 catch 中的代碼

如果沒有找到匹配的例外型別, 就會將例外向上傳遞到上層呼叫者.

無論是否找到匹配的例外型別, finally 中的代碼都會被執行到(在該方法結束之前執行).

如果上層呼叫者也沒有處理的了例外, 就繼續向上傳遞.

一直到 main 方法也沒有合適的代碼處理例外, 就會交給 JVM 來進行處理, 此時程式就會例外終止.

3.為什么要使用例外?

存在即合理,舉個例子

             //不使用例外
        int[] arr = {1, 2, 3};

        System.out.println("before");

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

        System.out.println("after");

當我們不使用例外時,發現出現例外程式直接崩潰,后面的after也沒有列印,

               //使用例外
        int[] arr = {1, 2, 3};

        try {

            System.out.println("before");

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

            System.out.println("after");

        } catch (ArrayIndexOutOfBoundsException e) {
            //	列印出現例外的呼叫堆疊

            e.printStackTrace();

        }

        System.out.println("after try catch");

當我們使用了例外,雖然after也沒有執行,但程式并沒有直接崩潰,后面的sout陳述句還是執行了

這不就是例外的作用所在嗎?

再舉個例子,當玩王者榮耀時,突然斷網,他不會讓你直接程式崩潰吧,而是給你斷線重連的機會吧:

我們再用偽代碼演示一把王者榮耀的對局程序:

不使用例外處理
boolean ret = false;

ret = 登陸游戲();

if (!ret) {

處理登陸游戲錯誤;

return;

}

ret = 開始匹配();

if (!ret) {

處理匹配錯誤;

return;

}
ret = 游戲確認();

if (!ret) {

處理游戲確認錯誤;

return;

}
ret = 選擇英雄();

if (!ret) {

處理選擇英雄錯誤;

return;

}

ret = 載入游戲畫面();

if (!ret) {

處理載入游戲錯誤;

return;

}

......
使用例外處理
try {

登陸游戲();

開始匹配();

游戲確認();

選擇英雄();

載入游戲畫面();

...

} catch (登陸游戲例外) {

處理登陸游戲例外;

} catch (開始匹配例外) {

處理開始匹配例外;

} catch (游戲確認例外) {

處理游戲確認例外;

} catch (選擇英雄例外) {

處理選擇英雄例外;

} catch (載入游戲畫面例外) {

處理載入游戲畫面例外;

}
......

我們能明顯的看到不使用例外時,正確流程和錯誤處理代碼混在一起,不易于分辨,而用了

例外后,能更易于理解代碼,

當然使用例外的好處還遠不止于此,我們可以在try、catch陳述句中加入資訊提醒功能,比如你

開發了一個軟體,當那個軟體出現例外時,發個資訊提醒你及時去修復,博主就做了一個小

小的qq郵箱資訊提醒功能,原始碼在碼云,有興趣的可以去看看呀!需要配置qq郵箱pop3服

務,友友們可以去查查怎么開啟呀,我們主旨不是這個所以不教怎么開啟了,演示一下:

別群發訊息哦,不然可能會被封號???

例外應只用于例外的情況

try{
   int i = 0;
   while(true)
       System.out.println(a[i++]);
}catch(ArrayIndexOutOfBoundsException e){
 }

這段代碼有什么用?看起來根本不明顯,這正是它沒有真正被使用的原因,事實證明,作為

一個要對陣列元素進行遍歷的實作方式,它的構想是非常拙劣的,當這個回圈企圖訪問陣列

邊界之外的第一個陣列元素時,用拋出(throw)、捕獲(catch)、

忽略(ArrayIndexOutOfBoundsException)的手段來達到終止無限回圈的目的,假定它與數

組回圈是等價的,對于任何一個Java程式員來講,下面的標準模式一看就會明白:

for(int m : a)
   System.out.println(m);

為什么優先例外的模式,而不是用行之有效標準模式呢?


可能是被誤導了,企圖利用例外機制提高性能,因為jvm每次訪問陣列都需要判斷下標是否越

界,他們認為回圈終止被隱藏了,但是在foreach回圈中仍然可見,這無疑是多余的,應該避

免,

上面想法有三個錯誤:

1.例外機制設計的初衷是用來處理不正常的情況,所以JVM很少對它們進行優化,

2.代碼放在try…catch中反而阻止jvm本身要執行的某些特定優化,

3.對陣列進行遍歷的標準模式并不會導致冗余的檢查,

這個例子的教訓很簡單:顧名思義,例外應只用于例外的情況下,它們永遠不應該用于正常

的控制流,

總結:例外是為了在例外情況下使用而設計的,不要用于一般的控制陳述句,

4. 例外的種類

在Java中提供了三種可拋出結構:受查例外(checked exception)、運行時例外(run-time exception)和錯誤(error),

(補充)

4.1 受查例外

什么是受查例外?只要不是派生于error或runtime的例外類都是受查例外,舉個例子:

我們自定義兩個例外類和一個介面,以及一個測驗類

interface IUser {
    void changePwd() throws SafeException,RejectException;
}

class SafeException extends Exception {//因為繼承的是execption,所以是受查例外類

    public SafeException() {

    }

    public SafeException(String message) {
        super(message);
    }

}

class RejectException extends Exception {//因為繼承的是execption,所以是受查例外類

    public RejectException() {

    }
    public RejectException(String message) {
        super(message);
    }
}

public class Test {
    public static void main(String[] args) {
        IUser user = null;
        user.changePwd();
    }
}

我們發現test測驗類中user使用方法報錯了,因為java認為checked例外都是可以再編譯階

段被處理的例外,所以它強制程式處理所有的checked例外,java程式必須顯式處checked

例外,如果程式沒有處理,則在編譯時會發生錯誤,無法通過編譯,

解決方案:

①try、catch包裹

 IUser user = null;
        try {
            user.changePwd();
        }catch (SafeException e){
            e.printStackTrace();
        }
        catch (RejectException e){
            e.printStackTrace();
        }

②拋出例外,將處理動作交給上級呼叫者,呼叫者在呼叫這個方法時還是要寫一遍try、catch

包裹陳述句的,所以這個其實是相當于宣告,讓呼叫者知道這個函式需要拋出例外

public static void main(String[] args) throws SafeException, RejectException {
        IUser user = null;
        user.changePwd();
    }

4.2非受查例外

派生于error或runtime類的所有例外類就是非受查例外,

可以這么說,我們現在寫程式遇到的例外大部分都是非受查例外,程式直接崩潰,后面的也

不執行,

像空指標例外、陣列越界例外、算術例外等,都是非受查例外,由編譯器運行時給你檢查出

來的,所以也叫作運行時例外,

5.如何使用例外

避免不必要的使用受查例外

如果不能阻止例外條件的產生,并且一旦產生例外,程式員可以立即采取有用的動作,這種

受查例外才是可取的,否則,更適合用非受查例外,這種例子就是

CloneNotSuppportedException(受查例外),它是被Object.clone拋出來的,Object.clone

只有在實作了Cloneable的物件上才可以被呼叫,

被一個方法單獨拋出的受查例外,會給程式員帶來非常高的額外負擔,如果這個方法還有其

他的受查例外,那么它被呼叫是一定已經出現在一個try塊中,所以這個例外只需要另外一個

catch塊,但當只拋出一個受查例外時,僅僅一個例外就會導致該方法不得不處于try塊中,也

就導致了使用這個方法的類都不得不使用try、catch陳述句,使代碼可讀性也變低了,

受查例外使介面宣告脆弱,比如一開始一個介面只有一個宣告例外

interfaceUser{  
    //修改用戶名,拋出安全例外  
    publicvoid changePassword() throws MySecurityExcepiton; 
} 

但隨著系統開發,實作介面的類越來越多,突然發現changePassword還需要拋出另一個異

常,那么實作這個介面的所有類也都要追加對這個新例外的處理,這個工程量就很大了,

總結:如果不是非用不可,盡量使用非受查例外,或將受查例外轉為非受查例外,

6.自定義例外

我們用自定義例外來實作一個登錄報錯的小應用

class NameException extends RuntimeException{//用戶名錯誤例外
    public NameException(String message){
        super(message);
    }
}
class PasswordException extends RuntimeException{//密碼錯誤例外
    public PasswordException(String message){
        super(message);
    }
}

test類來測驗運行

public class Test {
    private static final String name = "bit";
    private static final String password ="123";

    public static void Login(String name,String password) throws NameException,PasswordException{
        try{
            if(!Test.name.equals(name)){
                throw new NameException("用戶名錯誤!");
            }
        }catch (NameException e){
            e.printStackTrace();
        }
        try {
            if(!Test.password.equals(password)){
                throw new PasswordException("密碼錯誤");
            }
        }catch (PasswordException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        String password = scanner.nextLine();
        Login(name,password);
    }
}

關于例外就到此為止了,怎么感徑訓有點意猶未盡呢?

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/402728.html

標籤:java

上一篇:【JavaSE系列】趁著跨年的時間,學會了Java中常用的比較器和克隆介面

下一篇:synchronized和volatile關鍵字實作和底層原理詳解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more