使用 return 的 Try-finally 块不会引发异常

Try-finally block with return doesn't rise exception

在这段代码中,main 方法的 Catch 没有捕获运行时异常。 finally块执行完后,本该进入main的异常块,但是没有。

 class FinallyDemo {
    static int m1(){
        try{
            System.out.println("Inside m1");
            throw new RuntimeException("hi");
        }
        finally {
            System.out.println("m1 finally");
            return 5;
        }
    }
    public static void main(String[] args) {
        try{
            System.out.println(m1());
        }catch (Exception e){
            System.out.println("main caught: "+ e);
        }
    }
}

输出:

Inside m1
m1 finally
5

(如评论中所述,编写的代码甚至无法编译,但可以在不改变问题核心的情况下修复。)

it should've gone to the exception block of main, but it doesn't.

不,它的行为完全符合规范 section 14.20.2。那里有很多路径,但这里应用的路径是:

  • ...
  • If execution of the try block completes abruptly because of a throw of a value V
    • ...
    • If the run-time type of V is not assignment compatible with a catchable exception class of any catch clause of the try statement, then the finally block is executed
      • ...
      • If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and the throw of value V is discarded and forgotten).

根据 section 14.1return 语句算作“突然完成”。所以整个 try/finally 语句由于“return 有一个值”的原因突然完成,就好像没有抛出异常一样。

如果您希望异常传播到 try/finally 语句之外,请不要在 finally 块中使用 return 语句。

这是 class 的字节码。

static int m1();
    descriptor: ()I
    flags: ACC_STATIC
    Code:
      stack=3, locals=1, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Inside m1
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: new           #5                  // class java/lang/RuntimeException
        11: dup
        12: ldc           #6                  // String hi
        14: invokespecial #7                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
        17: athrow
        18: astore_0
        19: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        22: ldc           #8                  // String m1 finally
        24: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: iconst_5
        28: ireturn
      Exception table:
         from    to  target type
             0    19    18   any

查看此程序后。

public class FinallyDemo {
    static void m1(){
        try{
            System.out.println("Inside m1");
            throw new RuntimeException("hi");
        }
        finally {
            System.out.println("m1 finally");
        }
    }
    public static void main(String[] args) {
        try{
            m1();
        }catch (Exception e){
            System.out.println("main caught: "+ e);
        }
    }
}

字节码。

 static void m1();
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=1, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Inside m1
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: new           #5                  // class java/lang/RuntimeException
        11: dup
        12: ldc           #6                  // String hi
        14: invokespecial #7                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
        17: athrow
        18: astore_0
        19: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        22: ldc           #8                  // String m1 finally
        24: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: aload_0
        28: athrow
      Exception table:
         from    to  target type
             0    19    18   any

在我看来,athrow本质上是一个跳转语句。 很明显,在finally中加入了一条return语句,编译时省略了athrow语句,因为确定不可达