一文搞懂Java異常是個什麼東東?

一、Java異常簡介 

大家對trycatch可能並不陌生,也都使用的非常熟練了。

當程序運行過程中發生錯誤時,就會拋出異常,拋出異常總比終止程序來的好的多。

也可以在已知某個錯誤要發生時,進行trycatch操作,異常時進行某些特有操作。

1、Exception和Error

Exception和Error都繼承於Throwable 類,在 Java 中只有 Throwable 類型的實例纔可以被拋出或捕獲,它是異常處理機制的基本組成類型。

Exception是可預料的異常情況,可以獲取到這種異常,並對其進行業務外的處理。

Error是不可預料的異常,error發生後,會直接導致JVM不可處理。

Exception分爲檢查性異常、非檢查性異常。

檢查性異常,必須在編寫代碼時,使用try catch捕獲(比如:IOException異常)。

非檢查性異常,編譯器不會發現這個地方是否會產生一次,比如空指針異常,這種異常是在代碼編寫或者使用過程中通過規範可以避免發生的。比如sts的findbugs功能就可以檢測到代碼的空指針異常。

2、NoClassDefFoundError 和 ClassNotFoundException 有什麼區別?

NoClassDefFoundError是JVM運行時通過classpath加載類時,找不到對應的類而拋出的錯誤。

ClassNotFoundException:如果在編譯過程中可能出現此異常,在編譯過程中必須將其拋出。

NoClassDefFoundError的發生場景:

  1. 類依賴的class或jar不存在
  2. 類文件存在,但是在不同的域中,簡而言之,就是找不到

ClassNotFoundException的發生場景:

  1. 調用class的forName方法時,找不到指定的類
  2. ClassLoader中的findSystemClass() 方法時,找不到指定的類
public static void main(String[] args) {
    try {
        Class.forName("test");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

二、trycatch語法

1、try語句

try語句用大括號包含一段代碼,該段代碼可能會拋出一個或多個例外。

2、catch語句

catch語句的參數類似於方法的聲明,包括一個例外類型和一個例外對象。例外類型必須爲Throwable類的子類,它指明瞭catch語句所處理的例外類型,例外對象則由運行時系統在try所指定的代碼塊中生成並被捕獲,大括號中包含對象的處理,其中可以調用對象的方法。

catch語句可以有多個,分別處理不同類的例外。Java運行時系統從上到下分別對每個catch語句處理的例外類型進行檢測,直到找到類型相匹配的catch語句爲止。這裏,類型匹配指catch所處理的例外類型與生成的例外對象的類型完全一致或者是它的父類,因此,catch語句的排列順序應該是從特殊到一般。

3、finally語句

不管try中是否會拋出例外,finally語句中的代碼都會執行,finally 語句塊的最重要的作用應該是釋放申請的資源。

4、throws語句

throws總是出現在函數頭後,用來標明該方法可能拋出的異常。

5、throw語句

與throws異曲同工,只是位置不同,throw放在catch模塊中,程序會在throw執行後立即終止,throw後的代碼不執行了,finally除外。

6、拋出異常

public void test() throws Exception{
    throw new Exception();
};

7、捕獲異常

try{
    //代碼區
}catch(Exception e){
    log.error("error: {}", e);
}finally{
    //最後必須執行的部分
}

三、trycatch的執行順序

  1. 從try中第一行代碼開始執行,執行到出現異常的代碼,JVM會創建一個異常對象。
  2. 判斷catch是否能捕獲到jvm創建的異常對象,① 如果捕獲到就跳到catch代碼塊中,不會結束程序,繼續從catch中的代碼邏輯;② 如果捕獲不到,直接打印異常信息並結束程序。
  3. 如果try中沒有異常,則執行完try中代碼,跳過catch,進入finally代碼塊。

四、異常處理原則

  1. 方法內如果拋出需要檢測的異常,那麼方法上必須要聲明,否則必須在方法內用try-catch捕捉,否則編譯失敗。
  2. 如果調用了聲明異常的函數,要麼try-catch要麼throws,否則編譯失敗。
  3. 什麼時候catch,什麼時候throws?功能內容可以解決,用catch,解決不了,用throws告訴調用者,有調用者解決。
  4. 如果一個功能拋出了多個異常,那麼調用時必須有對應多個catch進行鍼對性的處理。

五、工作過程中總結的使用try的注意事項

  1. 儘量不要直接捕獲Exception,而是應該先捕獲特定異常,最後捕獲Exception異常。因爲寫代碼也是一門學問,要讓讀代碼的人可以從代碼中獲取更多的信息。
  2. try中不應放過多的代碼,只放必須放的就可以了,放太多的話如果發生異常,異常後面的代碼將不會被執行。
  3. catch可以有多個,而捕獲的異常,應按優先級捕獲,先捕獲最低級別的異常,以此類推。
  4. finally在trycatch語法中是選用的,不必須有,但如果有,不管是否發生異常,finally中的代碼都會最後執行,finally中常見的操作是對流進行關閉,或者刪除某些操作完的文件等。
  5. 當你對一段代碼進行trycatch時,catch中一定要做處理,一般會給定返回值,boolean類型、整形一般根據實際情況返回-1、String或者集合類型一般返回null,然後再調用處進行返回值判斷,堅決避免catch中不做任何處理,亦或返回了,但沒接收。
  6. 當有業務需要時,可以用trycatch進行邏輯判斷,報錯時是一種邏輯的判斷,有的時候很方便,也很好用。

六、禁用e.printStackTrace() 

禁止在代碼中出現e.printStackTrace() 。

e.printStackTrace() 只會將錯誤信息打印在控制檯(一般用log輸出日誌替代),產生的錯誤字符串會到字符串內存空間,此內存空間一旦被佔滿,就沒空間進行其它操作了,大量其它的線程就會中止,等待內存空間的釋放,相互等待,等內存,鎖住了,整個應用就掛掉了。

七、trycatch內代碼越少越好嗎?

民間有這樣一個說法,trycatch的範圍越小越好,這個誰都知道,爲什麼呢?因爲效率低。爲什麼效率低呢?不知道了。

trycatch與沒有trycatch的代碼,區別在於,前者阻止了Java的重排序,trycatch裏面的代碼不會被編譯器優化重排。重排序是編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。

簡而言之,try會阻止Java對代碼進行重排序,也就是放棄了Java對代碼的優化。效率肯定比重排序低,所以說,try中的代碼越少越好。

當然,如果你不能很好的把握哪裏會爆異常,哪裏不會,在保證代碼正確運行的前提下,再去嘗試性的縮小trycatch的範圍吧。

而代碼的重排序最關鍵的地方就是即時編譯器,下面來介紹一下即時編譯器,也是本篇的重點。

八、即時編譯器

即時編譯器JIT是一個把Java字節碼轉換成可以直接發送給處理器的執行令的程序,用於提高運行時Java應用程序的性能。

在運行時,JVM裝載類文件,確定每個單獨的字節碼的含義,並執行相應的計算,解釋期間額外使用處理器和內存就意味着Java應用程序的執行速度要比本機應用程序慢,這是肯定的,因爲代碼還需要編譯嘛。

JIT編譯器通過在運行時將字節碼編譯爲本機代碼以幫助提高Java程序的性能。

在編譯方法時,JVM直接調用編譯好的本地代碼,而不需要再去編譯了。理論上,如果編譯不需要佔用處理器時間和內存,那麼編譯每個方法都可能使Java程序的執行速度接近於本機應用程序的速度。

實際上,第一次調用方法時不會對方法進行編譯。對於每個方法,JVM都會保留一個調用計數,每次調動方法時該計數都將遞增,JVM對方法進行解釋,直至其調用計數超過JIT編譯閾值。因此,JVM啓動後將立即編譯常用方法,而在較長時間之後再編譯不常用方法,或者直接不編譯不常用方法。JIT編譯閾值幫助JVM快速啓動並且還能提高性能,謹慎選擇閾值,在啓動和長期運行之間實現最佳平衡。

在編譯方法後,調用計數將重置爲0,並且對該方法的後續調用將繼續使其計數遞增。在達到閾值時,JIT編譯器將執行第二次編譯,與前一次的編譯相比,其優化選擇更多,此過程循環往復,直至達到最大優化級別。Java程序中被調用次數最多的方法,代碼的優化處理做的是最好的,這也是提倡提取共通方法的原因。

簡而言之,就是執行越頻繁的代碼,Java對其的優化越好,執行速度越快。

九、trycatch小demo

1、代碼實例

public class ExceptionTest {
    private boolean test01() {
        boolean ret = true;
        try {
            ret = test02();
        } catch (Exception e) {
            System.out.println("test01 error:" + e.getMessage());
            ret = false;
        } finally {
            System.out.println("test01,finally, return -> " + ret);
            return ret;
        }
    }
 
    private boolean test02() {
        boolean ret = true;
        try {
            //問題1:test03發生異常了,雖然trycatch了,但調用的地方沒有接收
            test03();
            System.out.println("因爲test03報錯,我不應該執行。");
            return ret;
        } catch (Exception e) {
            System.out.println("test02 error:"+e.getMessage());
            ret = false;
            throw e;
        } finally {
            System.out.println("test02 finally, return -> " + ret);
            return ret;
        }
    }
 
    private boolean test03() throws Exception {
        boolean ret = true;
        try {
            System.out.println("我是CSDN哪吒");
            System.out.println("即將發生異常");
            int a = 1/0;
            System.out.println("發生異常後,還有走我嗎?");
            return true;
        } catch (Exception e) {
            System.out.println("test03 error:"+e.getMessage());
            ret = false;
            throw e;
        } finally {
            System.out.println("test03 finally, return -> " + ret);
            return ret;
        }
    }
 
    public static void main(String[] args) {
        ExceptionTest main = new ExceptionTest();
        try {
            main.test01();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2、控制檯輸出

3、思考一個問題

當需要在循環中使用trycatch的時候,try放for外面好,還是裏面好呢?

當兩者沒有發生異常時,兩者的效率其實是一樣的。

十、常見異常

1、Exception常見的子類

2、RuntimeException常見的子類

3、Error類的常見子類

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章