【Java】try、catch、finally的执行顺序与返回值问题

时间:2021-11-17 20:19:53

笔试很多时候都会考try、catch、finally的执行顺序,网上也有很多文章记录他们的执行顺序,但是都是根据代码执行结果推规律,有种死记硬背的感觉。从字节码反编译后的代码可以查看try、catch、finally执行顺序以及返回值,从字节码层面上来看他们的执行顺序。

Java之所以能够 Write Once,Run Anywhere,关键在于Java编译后的生成字节码文件.class,一个.java文件对应一个.class文件。Java程序运行,就是JVM运行这些字节码文件。字节码文件对所有平台版本的JVM都是一样的,所以编译一次后的Java程序可以在各个平台运行,本质就是字节码文件兼容各个平台版本的JVM。

在cmd中

  • javac Main.java
    可以将某个.java文件编译成字节码文件,编译成功会生成对应文件名字的.class文件,里面都是字节码,十六进制。
  • javap -c Main.class
    可以查看某个字节码文件反编译后的代码,就是JVM要执行的指令,不同版本的JVM,反编译后的指令可能是有差异的。

下面从几个栗子来探究一下try、catch、finally块到底是怎样一回事。

public class Main{

    public void test() {
        try {  
            tryItOut();
        } catch(Exception e) {
            catchItOut();
        } finally {  
            wrapItUp();  
        }  
    }

    public void tryItOut() {}  

    public void catchItOut() {}

    public void wrapItUp() {}  
}
//Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0       
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return        

  public void test();
    Code:
       0: aload_0       
       1: invokevirtual #2 // Method tryItOut:()V
       4: aload_0       
       5: invokevirtual #3 // Method wrapItUp:()V
       8: goto          30
      11: astore_1      
      12: aload_0       
      13: invokevirtual #5 // Method catchItOut:()V
      16: aload_0       
      17: invokevirtual #3 // Method wrapItUp:()V
      20: goto          30
      23: astore_2      
      24: aload_0       
      25: invokevirtual #3 // Method wrapItUp:()V
      28: aload_2       
      29: athrow        
      30: return        
    Exception table:
       from    to  target type
           0     4    11   Class java/lang/Exception
           0     4    23   any
          11    16    23   any

  public void tryItOut();
    Code:
       0: return        

  public void catchItOut();
    Code:
       0: return        

  public void wrapItUp();
    Code:
       0: return        
}

0-28指令都是test方法中的指令。查看Exception table,可以得知,0-4指令如果出现异常java.lang.Exception,则跳到指令11,如果出现其他任何异常则跳到指令23,在11-16指令处如果出现任何异常,则跳到指令23。
结合代码来看,

  • 指令0-4是try块中的代码,0-4指令正常执行,则执行指令5,然后go to指令30,return也就是该方法结束。如果此处出现异常,两种情况

    • 出现java.lang.Exception,则跳到指令11,也就是执行catch块中的代码。
    • 出现除了java.lang.Exception以外其他任何异常,跳到指令23,也就是执行finally块代码,最后返回。
  • 指令11-16是catch块中的代码,如果正常执行,则执行指令17,也就是finally块,如果此处出现异常,则跳到指令23,执行finally块。

总结来看,主要有三种情况:

  • 无任何异常,则执行try块后代码,再执行finally块代码,返回
  • try中出现异常,并且可以被catch捕捉,则是执行try块代码,再执行catch块代码,最后执行finally代码,返回;如果try中出现的异常,不能被catch捕捉,则是执行try块代码,再执行finally代码,返回
  • try中出现异常,并且可以被catch捕捉,catch出现异常,则是执行try块代码,再执行catch块代码,最后执行finally代码,返回

也就是说finally代码无论如何都会执行的,主要看会不会执行catch块。另外,可以看出finally中出现异常则不会被处理,如果finally中出现异常应该用catch处理,避免finally中出现异常。

public class Main{

    public int test() {
        try {  
            tryItOut();
            return 1;
        } catch(Exception e) {
            catchItOut();
            return 2;
        } 
        finally {  
            wrapItUp();
            return 3;
        }  
    }

    // auxiliary methods 
    public void tryItOut() {}  

    public void catchItOut() {}

    public void wrapItUp() {}  

    public static void main(String[] args) {
        System.out.println(new Main().test());    
    }
}
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0       
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return        

  public int test();
    Code:
       0: aload_0       
       1: invokevirtual #2 // Method tryItOut:()V
       4: iconst_1      
       5: istore_1      
       6: aload_0       
       7: invokevirtual #3 // Method wrapItUp:()V
      10: iconst_3      
      11: ireturn       
      12: astore_1      
      13: aload_0       
      14: invokevirtual #5 // Method catchItOut:()V
      17: iconst_2      
      18: istore_2      
      19: aload_0       
      20: invokevirtual #3 // Method wrapItUp:()V
      23: iconst_3      
      24: ireturn       
      25: astore_3      
      26: aload_0       
      27: invokevirtual #3 // Method wrapItUp:()V
      30: iconst_3      
      31: ireturn       
    Exception table:
       from    to  target type
           0     6    12   Class java/lang/Exception
           0     6    25   any
          12    19    25   any

  public void tryItOut();
    Code:
       0: return        

  public void catchItOut();
    Code:
       0: return        

  public void wrapItUp();
    Code:
       0: return        

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #6 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #7 // class Main
       6: dup           
       7: invokespecial #8 // Method "<init>":()V
      10: invokevirtual #9 // Method test:()I
      13: invokevirtual #10 // Method java/io/PrintStream.println:(I)V
      16: return        
}

如上分析可以得到,执行步骤也是一致的。
除此之外,需要注意的是return值。
return的值是就近原则,返回最近存储的值。
譬如try中代码正常执行,优先返回finally值,如果finally没有返回值,则返回try中的返回值。
什么情况返回catch中的返回值呢?根据字节码可以得到当异常被catch捕捉,finally没有返回值,则返回catch中的返回值。
什么时候返回try,catch,finally代码块之外的返回值呢?那么应该是这种情况,没有被指定到try、catch的return代码,并且finally没有返回值。如下代码是一种情况。

这么看来的话,其实try-catch-finally执行顺序跟返回值都跟指令的执行顺序一致,万变不离其宗,这样再来看这些问题的时候,就不是死记硬背,而是比较条理清晰了。

参考:http://blog.csdn.net/FeeLang/article/details/40273869

相关文章