Java-異常體系

Java-異常體系

sschrodinger

2019/03/08


基于 JAVA API 11

參考 關(guān)于 Java 中 finally 語句塊的深度辨析

參考 JLS 標(biāo)準(zhǔn)

參考 JVM 標(biāo)準(zhǔn)


異常體系的分類


在 Java 異常體系中,所有的異常都繼承自 Throwable 類,具體又分為異常錯誤兩種異常。

錯誤是繼承了 Throwable 類的 Error 類及其子類的所有類,比較典型的是 OutOfMemoryErrorStackOverflowError 兩種錯誤。所有的錯誤都會被 Java 虛擬機捕獲,表示的是不可挽回的錯誤,程序員沒有方法對這些錯誤進行處理。

異常是繼承了 Throwable 類的 Exception 類及其子類的所有類,又分為檢查型異常和非檢查型異常,檢查型異常(Checked Exception)指的是需要程序員對此異常進行處理,包括捕捉或拋出錯誤,非檢查型異常(Unchecked Exception)指的是不需要程序員對此異常進行處理。

JVM 和 Java 編譯器會對異常進行檢查,所有RuntimeException 類都是屬于非檢查型異常,包括RuntimeExceptu+ionNullPointerException 等,其他的都屬于檢查型異常。


異常的使用


對于檢查型異常,必須對其進行捕獲或拋出到下一級函數(shù),拋出用關(guān)鍵詞 throws 申明,捕獲使用 try{}catch{}finally{} 語句塊申明。

代碼如下:

public class Demo() {
    
    public function_1() {
        try {
            //使用 try catch 捕獲錯誤
            function_2();
        } catch(IOException e) {
            
        } finally {
            //可選項
        }
    }
    //如果不捕獲,則函數(shù)申明必須拋出錯誤
    public function_2() throws IOException {
        function_3();
    }
    
    //如果不捕獲,則函數(shù)申明必須拋出錯誤
    public finction_3() throws IOException {
    
        //拋出一個非檢查異常
        throw new IOException();
    }
    
}

對于檢查型異常,如果沒有拋出或者捕獲錯誤,編譯器會報錯,但是對于非檢查型錯誤,編譯器不強制要求進行捕捉或者拋出,如下:

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Test tset = new Test();
        tset.testFunc();
    }
    
    public void testFunc() {
        //也可進行捕獲
        try {
            testFunc1();
        } catch (DemoUnCheckedException e) {
            // TODO: handle exception
            System.out.println(e.getClass().getName());
        }
    }
    
    //在此處可以不進行捕獲或者拋出
    public void testFunc1() {
        testFunc2();
    }
    
    public void testFunc2() {
        throw new DemoUnCheckedException();
    }
}

class DemoUnCheckedException extends RuntimeException {
    
}

try-catch-finally 語句塊


問題一:當(dāng)try中有return語句時,finally語句是否會運行?

答案是會。

oracle的官方文檔解答了這個問題。

the finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handing - it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in finally blockis always a good practice, even when no exception are anticipated.

Note

  • If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, If the thread executing the try or catch code is interruped or killed, the finally block may not execute even though the application as a whole continues.

原文翻譯如下:當(dāng)try語句退出時肯定會執(zhí)行finally語句。這確保了即使是發(fā)生了一個意想不到的異常也會執(zhí)行finally語句塊。但是finally的用處不僅是用來處理異常-它可以讓程序員不會因為return、continue或者break語句而忽略了清理代碼的工作。把清理代碼放在finally語句塊中是一個很好的做法,即使可能不會有異常發(fā)生也要這么做。

問題二:finally語句塊執(zhí)行的時機?

當(dāng) try-catch 語句塊中,沒有流程跳轉(zhuǎn)指令時,如沒有 return、break、continue時,finally語句塊的執(zhí)行會緊跟在 try-catch 之后,但是當(dāng) try-catch 中有流程跳轉(zhuǎn)指令時,finally 語句塊的執(zhí)行順序就變得很復(fù)雜。

首先來看一個例子:

public class Test { 
    public static void main(String[] args) {  
        testFunc();
        System.out.println("last function");
    } 
    
    public static void testFunc() {
        try {  
            System.out.println("try block");  
            return;
        } finally {  
            System.out.println("finally block");  
        }  
    }
}
//output:
//try block
//finally block
//last function

可以看到,finally 語句塊在返回之前執(zhí)行。

更準(zhǔn)確的說,finally 語句都是在控制轉(zhuǎn)移語句之前執(zhí)行。Java 語言規(guī)范如下:

JLS

  • Afinallyclause can also be used to clean up forbreak,continue, andreturn, which is one reason you will sometimes see atryclause with nocatchclauses. When any control transfer statement is executed, all relevantfinallyclauses are executed. There is no way to leave atryblock without executing itsfinallyclause.

問題三:當(dāng) finally 語句塊和 try-catch 語句塊都有返回值時,返回值是什么?

返回值為 finally 語句塊中的值。

我們看如下代碼:

public class Test { 
public static void main(String[] args) { 
       System.out.println("return value of getValue(): " + getValue()); 
    } 
 
public static int getValue() { 
       int i = 1; 
       try { 
                return i; 
       } finally { 
                i++; 
       } 
    } 
}
//output:
//return value of getValue(): 1

原理比較簡單,當(dāng)執(zhí)行到 return i 時,會將返回值緩存在變量空間中,返回時直接返回緩存的值。

這在 JVM 規(guī)范中也有規(guī)定,規(guī)定原文如下:

JVM Specification

  • If the try clause executes a return, the compiled code dose the folloing:
    • Saves teh return value(if any) in a local variable.
    • Executes a jsr to the code for the finally clause
    • Upon return from the finally clause, returns the value saves in the local variable.

即在第一次執(zhí)行return語句時,就保存了返回值在本地變量中。

這主要是 Java 編譯時實現(xiàn)的功能,看上面示例 class 文件如下:

public class Test extends java.lang.Object{ 
public Test(); 
 Code: 
  0:    aload_0 
  1:invokespecial#1; //Method java/lang/Object."<init>":()V 
  4:    return 
 
 LineNumberTable: 
  line 1: 0 
 
public static void main(java.lang.String[]); 
 Code: 
  0:    getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream; 
  3:    new     #3; //class java/lang/StringBuilder 
  6:    dup 
  7:    invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V 
  10:   ldc     #5; //String return value of getValue(): 
  12:   invokevirtual   
  #6; //Method java/lang/StringBuilder.append:(
      Ljava/lang/String;)Ljava/lang/StringBuilder; 
  15:   invokestatic    #7; //Method getValue:()I 
  18:   invokevirtual   
  #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 
  21:   invokevirtual   
  #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
  24:   invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
  27:   return 
 
public static int getValue(); 
 Code: 
  0:    iconst_1 
  1:    istore_0 
  2:    iload_0 
  3:    istore_1 
  4:    iinc    0, 1 
  7:    iload_1 
  8:    ireturn 
  9:    astore_2 
  10:   iinc    0, 1 
  13:   aload_2 
  14:   athrow 
 Exception table: 
  from   to  target type 
    2     4     9   any 
    9    10     9   any 
}

我們看異常處理表,from to target 的含義時,如果在 formto 之間出現(xiàn)了異常,則從 taget 開始運行。

我們先看正常運行過程,正常程序在 code line 3 時,將 1 加入 正常本地變量緩存表,然后執(zhí)行 i++ ,即 第 4 行語句,第 5 行語句取出緩存表然后返回。

不正常的運行流程如下,當(dāng)從 2 到 4 這段指令出現(xiàn)異常時,將會產(chǎn)生一個 exception 對象,并且把它壓入當(dāng)前操作數(shù)棧的棧頂。接下來是 astore_2 這條指令,它負責(zé)把 exception 對象保存到本地變量表中 2 的位置,然后執(zhí)行 finally 語句塊,待 finally 語句塊執(zhí)行完畢后,再由 aload_2 這條指令把預(yù)先存儲的 exception 對象恢復(fù)到操作數(shù)棧中,最后由 athrow 指令將其返回給該方法的調(diào)用者(main)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 異常的繼承體系結(jié)構(gòu) Throwable 類是 Java 語言中所有錯誤或異常的超類。 只有當(dāng)對象是此類(或其子...
    凱玲之戀閱讀 20,756評論 5 18
  • 轉(zhuǎn)自:https://blog.csdn.net/liuhenghui5201/article/details/1...
    mayiwoaini閱讀 765評論 0 1
  • 首先,需要了解異常體系的結(jié)構(gòu): 看上面的結(jié)構(gòu),Throwable是所有異常的基類,有兩個子類:Error和Exce...
    _Once1閱讀 478評論 0 0
  • Error(錯誤): 是程序無法處理的錯誤,表示運行應(yīng)用程序中較嚴(yán)重問題。例如,Java虛擬機運行錯誤(Virtu...
    奔跑吧哈哈閱讀 751評論 0 10
  • My Chinese is not very good so send an English one for a ...
    愚公捕魚閱讀 227評論 0 0

友情鏈接更多精彩內(nèi)容