关于try、catch、finally的执行顺序与其中返回值不生效情况的探讨

先来看几段代码

/*代码块1*/
public class ExceptionTest
{
    public int inc()
    {
        int x;
        try{
            x=1;
            return x;
        }catch (Exception e)
        {
            x=2;
            return x;
        }
        finally
        {
            x=3;
        }
    }
    public static void main(String[]args)
    {

        ExceptionTest e=new ExceptionTest();
        int i=e.inc();
        System.out.println(i);
    }
}

/*代码块2*/
{
  public ExceptionTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=5, args_size=1
         0: iconst_1               /*声明常量1,并将其放入操作数栈顶*/
         1: istore_1
         2: iload_1					
         3: istore_2			   /*将1复制到Slot 2中*/ /*现在Slot1和Slot2都是1*/
         4: iconst_3			   /*finally块中声明的常量3*/
         5: istore_1			   /*将操作数栈顶的3放入Slot 1*//*Slot1:3;Slot2:1*/
         6: iload_2				   /*将1复制到操作数栈顶,⭐返回try修改过的slot2的值,而不是finally修改过的slot1值*/
         7: ireturn				   /*返回1*/
         
         8: astore_2				/*catch块开始,此句是将异常对象引用出栈放入Slot2中*/
         9: iconst_2				
        10: istore_1				/*将常量2出栈放入Slot1,⭐后面三行字节码做了个复制的操作*/
        11: iload_1					
        12: istore_3				/*局部变量表情况Slot1:2,Slot2:Exception,Slot3:2*/
        13: iconst_3				/*finally块中声明的常量3*/
        14: istore_1				/*局部变量表情况Slot1:3,Slot2:Exception,Slot3:2*/
        15: iload_3					/*⭐返回catch修改过的slot3的值,而不是finally修改过的slot1值*/
        16: ireturn					/*catch块中的return,此时将返回2*/
        
        17: astore        4			/*出现不属于Exception的异常则会跳转到这里*/
        19: iconst_3				/*finally块中声明的常量3*/
        20: istore_1				/*局部变量表情况Slot1:3,Slot4:不属于Exception的异常*/
        21: aload         4			
        23: athrow					/*抛出异常*/
      Exception table:
         from    to  target type
             0     4     8   Class java/lang/Exception
             0     4    17   any
             8    13    17   any
            17    19    17   any

}

/*代码块3*/
public class ExceptionTest
{
    public Instance inc()
    {
        Instance instance=new Instance();
        try{
            instance.flag=1;
            return instance;
        }catch (Exception e)
        {
            instance.flag=2;
            return instance;
        }
        finally
        {
            instance.flag=3;

        }
    }
    public static void main(String[]args)
    {

        ExceptionTest e=new ExceptionTest();
        Instance instance=e.inc();
        System.out.println(instance.flag);
    }
}
class Instance
{
    public int flag=0;

}
/*代码块4*/
public class ExceptionTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#27        // java/lang/Object."<init>":()V
   #2 = Class              #28            // Instance
   #3 = Methodref          #2.#27         // Instance."<init>":()V
   #4 = Fieldref           #2.#29         // Instance.flag:I
   #5 = Class              #30            // java/lang/Exception
   #6 = Class              #31            // ExceptionTest
   #7 = Methodref          #6.#27         // ExceptionTest."<init>":()V
   #8 = Methodref          #6.#32         // ExceptionTest.inc:()LInstance;
   #9 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #35.#36        // java/io/PrintStream.println:(I)V
  #11 = Class              #37            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               inc
  #17 = Utf8               ()LInstance;
  #18 = Utf8               StackMapTable
  #19 = Class              #31            // ExceptionTest
  #20 = Class              #28            // Instance
  #21 = Class              #30            // java/lang/Exception
  #22 = Class              #38            // java/lang/Throwable
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               SourceFile
  #26 = Utf8               ExceptionTest.java
  #27 = NameAndType        #12:#13        // "<init>":()V
  #28 = Utf8               Instance
  #29 = NameAndType        #39:#40        // flag:I
  #30 = Utf8               java/lang/Exception
  #31 = Utf8               ExceptionTest
  #32 = NameAndType        #16:#17        // inc:()LInstance;
  #33 = Class              #41            // java/lang/System
  #34 = NameAndType        #42:#43        // out:Ljava/io/PrintStream;
  #35 = Class              #44            // java/io/PrintStream
  #36 = NameAndType        #45:#46        // println:(I)V
  #37 = Utf8               java/lang/Object
  #38 = Utf8               java/lang/Throwable
  #39 = Utf8               flag
  #40 = Utf8               I
  #41 = Utf8               java/lang/System
  #42 = Utf8               out
  #43 = Utf8               Ljava/io/PrintStream;
  #44 = Utf8               java/io/PrintStream
  #45 = Utf8               println
  #46 = Utf8               (I)V
{
  public ExceptionTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public Instance inc();
    descriptor: ()LInstance;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=1
         0: new           #2                  // class Instance
         3: dup
         4: invokespecial #3                  // Method Instance."<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_1
         /*putfield的的具体操作时弹出两次栈顶,并根据第二次弹出的对象引用和putfield参数找到目标对象的成员变量,之后将第一次弹出的常量1赋值给这个成员变量*/
        10: putfield      #4                  //将1赋值给instance.flag
        13: aload_1							  
        14: astore_2						  /*局部变量表情况Slot1:instance对象,Slot2:instance对象*/
        15: aload_1								
        16: iconst_3						  /*finally块中的3*/
        17: putfield      #4                  //⭐将3赋值给instance.flag
        20: aload_2							  /*⭐重点注意这里是load Slot2而不是Slot1*/
        21: areturn							  /*返回对象引用*/
        
        22: astore_2						  /*catch块开始执行,此处将异常保存到Slot2*/
        23: aload_1
        24: iconst_2
        25: putfield      #4                  //将2赋值给instance.flag
        28: aload_1
        29: astore_3
        30: aload_1
        31: iconst_3						  /*finally块中的3*/
        32: putfield      #4                  //⭐将3赋值给instance.flag
        35: aload_3							  /*⭐重点注意这里是load Slot3而不是Slot1,即返回的是catch使用过的对象引用而不是finally使用过的对象引用*/
        36: areturn
        
        37: astore        4
        39: aload_1
        40: iconst_3						  /*finally块中的3*/
        41: putfield      #4                  // Field Instance.flag:I
        44: aload         4
        46: athrow
      Exception table:
         from    to  target type
             8    15    22   Class java/lang/Exception
             8    15    37   any
            22    30    37   any
            37    39    37   any

}

先讲一下Exception table的读法,如代码块2中的第一行,意义是:在字节码行数中若第0行到第4行出现了Exception异常,则跳去第8行继续执行,此时操作数栈顶是有一个异常对象的。

接下来说明普通的try-catch-finally的编译情况。可以看到,无论是代码块2或4或6,都被分成了(try-finally),(catch-finally)和(finally-抛出未知异常)这三大部分,⭐即finally块中编译后的代码被插入到了三个部分里,并将return语句移动到finally块里的代码执行完后再执行,这就是虚拟机对finally块的实现而虚拟机对异常的捕获则是通过查看异常表,根据异常表提供的异常出现位置和异常类型跳转到相应的字节码块中执行。

接下来说明一下在finally块中没有return语句时虚拟机对代码的编译情况。通过对比代码块2和4中画⭐的注释可以发现,虚拟机对这种情况处理的套路是:在try块执行完成后(字节码层面的执行完成),会对要返回的的值进行一次在局部变量表槽的复制。其后若finally块要使用这个变量,则会使用原槽中的变量;return语句则会返回复制后的槽中的变量,即finally块使用的和返回的并非是一个槽中的引用变量。所以这就会产生若是在finally块对对象成员变量修改,return时就有效(因为此时槽中是引用变量,是地址),而对基本数据类型如整型修改就无效的情况。(因为此时槽中是值)⭐(经测试,这个复制机制的出现条件是有finally块且try或catch块中有返回值,且只对这个try或catch块的返回值进行复制,无论finally块中有没有用到这个返回值)

接下来解释一下finally块中有return语句的编译情况。对比代码块6和代码块4,区别只有在第20行字节码中:finally块中有return语句时,将try和finally块的return合并到一起并放在了finally最后执行的areturn字节码 且会返回finally块使用过的槽的引用变量,所以此时对返回值的修改是必然有效的!

/*代码块5*/
public class ExceptionTest
{
    public Instance inc()
    {
        Instance instance=new Instance();
        try{
            instance.flag=1;
            return instance;
        }catch (Exception e)
        {
            instance.flag=2;
            return instance;
        }
        finally
        {
            instance.flag=3;
            return instance;
        }
    }
    public static void main(String[]args)
    {

        ExceptionTest e=new ExceptionTest();
        Instance instance=e.inc();
        System.out.println(instance.flag);
    }
}
class Instance
{
    public int flag=0;
}
/*代码块6*/
Constant pool:
   #1 = Methodref          #11.#27        // java/lang/Object."<init>":()V
   #2 = Class              #28            // Instance
   #3 = Methodref          #2.#27         // Instance."<init>":()V
   #4 = Fieldref           #2.#29         // Instance.flag:I
   #5 = Class              #30            // java/lang/Exception
   #6 = Class              #31            // ExceptionTest
   #7 = Methodref          #6.#27         // ExceptionTest."<init>":()V
   #8 = Methodref          #6.#32         // ExceptionTest.inc:()LInstance;
   #9 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #35.#36        // java/io/PrintStream.println:(I)V
  #11 = Class              #37            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               inc
  #17 = Utf8               ()LInstance;
  #18 = Utf8               StackMapTable
  #19 = Class              #31            // ExceptionTest
  #20 = Class              #28            // Instance
  #21 = Class              #30            // java/lang/Exception
  #22 = Class              #38            // java/lang/Throwable
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               SourceFile
  #26 = Utf8               ExceptionTest.java
  #27 = NameAndType        #12:#13        // "<init>":()V
  #28 = Utf8               Instance
  #29 = NameAndType        #39:#40        // flag:I
  #30 = Utf8               java/lang/Exception
  #31 = Utf8               ExceptionTest
  #32 = NameAndType        #16:#17        // inc:()LInstance;
  #33 = Class              #41            // java/lang/System
  #34 = NameAndType        #42:#43        // out:Ljava/io/PrintStream;
  #35 = Class              #44            // java/io/PrintStream
  #36 = NameAndType        #45:#46        // println:(I)V
  #37 = Utf8               java/lang/Object
  #38 = Utf8               java/lang/Throwable
  #39 = Utf8               flag
  #40 = Utf8               I
  #41 = Utf8               java/lang/System
  #42 = Utf8               out
  #43 = Utf8               Ljava/io/PrintStream;
  #44 = Utf8               java/io/PrintStream
  #45 = Utf8               println
  #46 = Utf8               (I)V
{
  public ExceptionTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public Instance inc();
    descriptor: ()LInstance;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=1
         0: new           #2                  // class Instance
         3: dup
         4: invokespecial #3                  // Method Instance."<init>":()V
         7: astore_1						  /*将调用<init>方法后返回的引用存到Slot 1*/
         8: aload_1
         9: iconst_1
        10: putfield      #4                  //将1赋值给instance.flag
        13: aload_1
        14: astore_2						  /*⭐就算后面的代码没有用到复制后的槽液依然将Slot 1的instance引用拷贝一份到Slot 2,证实了复制条件的猜测*/
        							          /*此时局部变量表情况Slot1:instance对象;Slot2:instance对象*/
        15: aload_1
        16: iconst_3						  /*finally块中声明的3*/
        17: putfield      #4                  //使用Slot1的instance引用,将3赋值给instance.flag
        20: aload_1							  /*⭐重点注意:此时返回的则是finally修改过的slot1,与上面finally块中没有return作比较*/
        21: areturn							  /*返回Slot 1的instance引用*/
        
        22: astore_2
        23: aload_1
        24: iconst_2
        25: putfield      #4                  // Field Instance.flag:I
        28: aload_1
        29: astore_3					     
        30: aload_1
        31: iconst_3
        32: putfield      #4                  // Field Instance.flag:I
        35: aload_1							   /*⭐重点注意:此时返回的同样是finally修改过的slot1,与上面finally块中没有return作比较*/
        36: areturn
        
        37: astore        4					  /*出现非Exception异常时会来到这里*/
        39: aload_1
        40: iconst_3
        41: putfield      #4                  // Field Instance.flag:I
        44: aload_1
        45: areturn							  /*⭐⭐这里依然是return,这会导致try块中如果出现不是Exception或其子类的异常时,这个异常会被吞掉,不会抛出到调用的栈帧;但finally块中出现异常时却仍然能报错*/
      Exception table:
         from    to  target type
             8    15    22   Class java/lang/Exception
             8    15    37   any
            22    30    37   any
            37    39    37   any
    
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: try catch finally执行顺序是先执行 try 块中的代码,如果出现异常则跳转到对应的 catch 块,执行 catch 块中的代码,最后无论是否出现异常,都会执行 finally 块中的代码。 ### 回答2: try catch finally 执行顺序是先执行 try 语句块中的代码,如果没有发生异常则跳过 catch 语句块,直接执行 finally 语句块中的代码。如果在 try 语句块中发生了异常,程序会跳出 try 块,然后寻找匹配的 catch 块来处理异常。如果找到了匹配的 catch 块,则执行这个块中的代码,并跳过 finally 块。如果没有找到匹配的 catch 块,则异常会传递到上层调用者,直到找到了匹配的 catch 块或程序结束。 无论是否发生异常,finally 语句块中的代码都会被执行,即使在 trycatch 块中有 return 语句。当异常被抛出时,会先执行 finally 块的代码,然后再将异常传递给上层调用者。 总结起来,try 语句块中的代码首先被执行,如果没有发生异常,则执行 finally 语句块中的代码;如果发生异常,则先执行 finally 语句块中的代码,然后执行匹配的 catch 语句块中的代码,最后再执行 finally 语句块中的代码。无论是否发生异常,finally 语句块中的代码都会被执行。 这种执行顺序的设计可以确保在程序执行过程中资源的释放和清理操作,即使发生了异常也能够得到处理。 ### 回答3: try catch finally 是一种错误处理机制。在程序执行过程中,当发生错误时,try语句块中的代码会被执行,如果没有错误发生,catchfinally语句块就不会执行。 try语句块中的代码被执行时,如果出现异常,程序会立即转到与之对应的catch语句块。catch语句块负责处理异常,并提供对应的异常处理逻辑。在catch语句块中,可以根据捕获到的异常类型进行相应的处理,例如输出错误信息、记录日志、回滚事务等。 无论是否发生异常,finally语句块中的代码都会被执行。这意味着,无论try块中的代码是否成功执行,finally语句块里的代码都会被执行到。finally语句块通常用于释放资源、回收内存等操作,在代码中确保必须执行的一段逻辑。 try catch finally执行顺序如下: 1. 程序首先执行try语句块中的代码。 2. 如果try语句块中的代码没有发生异常,catch语句块将被跳过。 3. 如果try语句块中的代码发生异常,运行时系统会查找与之对应的catch语句块,并执行相应的异常处理逻辑。 4. catch语句块执行完毕后,程序会继续执行finally语句块中的代码。 5. finally语句块中的代码执行完毕后,程序继续执行try catch finally块之后的代码。 总的来说,try catch finally块的执行顺序是:先执行try语句块,再执行catch语句块(如果有异常发生),最后执行finally语句块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值