在 catch 和 finally 子句中抛出异常

189

在大学Java课程中的一个问题中,有以下代码片段:

class MyExc1 extends Exception {}
class MyExc2 extends Exception {}
class MyExc3 extends MyExc2 {}

public class C1 {
    public static void main(String[] args) throws Exception {
        try {
            System.out.print(1);
            q();
        }
        catch (Exception i) {
            throw new MyExc2();
        }
        finally {
            System.out.print(2);
            throw new MyExc1();
        }
    }

    static void q() throws Exception {
        try {
            throw new MyExc1();
        }
        catch (Exception y) {
        }
        finally {
            System.out.print(3);
            throw new Exception();
        }
    }
}

我被要求给出它的输出。我回答说 13Exception in thread main MyExc2,但正确答案是 132Exception in thread main MyExc1。为什么?我不明白MyExc2去哪了。

12个回答

206

根据阅读您的答案并观察到您可能是这样想的,我相信您认为“正在进行的异常”具有“优先级”。请注意:

当在catch块或finally块中抛出一个新的异常,并将其传播出该块时,当前异常将被中止(并且被遗忘),因为新的异常向外传播。 新的异常开始像任何其他异常一样沿着堆栈向上展开,中止当前块(即catch或finally块),并受到沿途任何适用的catch或finally块的影响。

请注意,适用的catch或finally块包括:

当在catch块中抛出一个新异常时,如果有finally块,则新异常仍然受到该catch的finally块的影响。

现在回溯执行,记住每当您遇到throw时,应中止跟踪当前异常并开始跟踪新异常。


9
根据阅读你的答案和看到你可能是如何得出这个答案的,我认为你认为 "正在处理中的异常" 有 "优先权"。谢谢...那正是我的想法 :) - Jubstuff

64

在finally块中发生的异常优先于catch块中的异常。

Java语言规范

如果catch块因为原因R而意外完成,那么finally块会被执行。然后有两种选择:

  • 如果finally块正常完成,则try语句因原因R而突然终止。

  • 如果finally块因原因S而异常完成,则try语句因原因S而突然终止(原因R被丢弃)。


2
不要丢弃原因R,是否可以将原因R作为被抑制的异常添加到原因S中? - Nathan
@Nathan 这是我所预期的,但是语言规范实际上指出JVM不应该添加任何一个作为被抑制的异常,并明确指出R被丢弃了 :/ - undefined
哎呀,try-with-resources说:“关闭一个资源时发生的异常不会阻止其他资源的关闭。如果在初始化程序、try块或资源关闭时之前已经抛出了异常,那么这种异常将被抑制。”这与finally块非常不同 :/ https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-14.20.3 - undefined

42

以下是维基百科关于finally子句的解释:

更常见的是一个相关的子句(finally或ensure),无论是否发生异常都会执行, 通常用于释放在异常处理块内部获取的资源。

我们来分析一下你的程序。

try {
    System.out.print(1);
    q();
}

因此,屏幕上将输出1,然后调用q()。在q()中,抛出了一个异常。然后Exception y捕获了该异常,但什么也没做。接着执行finally子句(必须执行),因此3将被打印到屏幕上。由于在q()方法中,在finally子句中抛出了一个异常,并且q()方法通过方法声明中的throws Exception将异常传递给父堆栈,所以将抛出new Exception()并被catch (Exception i)捕获,MyExc2异常将被抛出(现在将其添加到异常堆栈中)。但是,在main块中的finally将首先执行。

因此,在

catch ( Exception i ) {
    throw( new MyExc2() );
} 
finally {
    System.out.print(2);
    throw( new MyExc1() );
}

如果在 try/catch 语句块中有 finally 子句,那么在捕获异常并将其抛出之前,finally 将被执行,然后在屏幕上打印出 2。之后,抛出了一个 MyExc1 异常,该异常由 public static void main(...) 方法处理。

输出:

"132 Exception in thread main MyExc1"

讲师是正确的! :-)

换言之,如果在 try/catch 语句块中使用了 finally 子句,那么在捕获异常和将其抛出之后,finally 将被执行。


catch 被执行了,因为 q() 在它自己的 finally 块中抛出了一个 Exception - Péter Török
q()中,会抛出一个异常,但在异常完全抛出之前,finally语句块首先被执行,因此,数字3将被打印到屏幕上。嗯...不,第一个在q中抛出的异常通过执行catch块被传递给空的catch块(它吞掉了这个异常),然后传递给q中的finally块。所述finally块打印3,然后抛出一个新的异常,由于qthrows Exception,该异常被传递到父级堆栈。 - Powerlord

25

无论在try/catch块中的任何位置抛出异常,finally子句都会被执行。

由于它是在main函数中最后被执行的,并且它抛出了一个异常,这就是调用者看到的异常。

因此,确保finally子句不会抛出任何异常非常重要,因为它可能会吞噬来自try块的异常。


6
即使在try/catch块中没有抛出任何异常,它也将被执行。 - nanda

10

一个方法不能同时抛出两个异常。它总是会抛出最后一个被抛出的异常,而在这种情况下,它总是来自于finally块。

当方法q()抛出第一个异常时,它将被catch并由finally块抛出的异常所替代。

q() -> 抛出new Exception -> main catch Exception -> throw new Exception -> finally 抛出一个新的异常(并且来自catch的那个异常被"丢失"了)


4
class MyExc1 extends Exception {}
class MyExc2 extends Exception {}
class MyExc3 extends MyExc2 {}

public class C1 {
    public static void main(String[] args) throws Exception {
        try {
            System.out.print("TryA L1\n");
            q();
            System.out.print("TryB L1\n");
        }
        catch (Exception i) {
            System.out.print("Catch L1\n");                
        }
        finally {
            System.out.print("Finally L1\n");
            throw new MyExc1();
        }
    }

    static void q() throws Exception {
        try {
            System.out.print("TryA L2\n");
            q2();
            System.out.print("TryB L2\n");
        }
        catch (Exception y) {
            System.out.print("Catch L2\n");
            throw new MyExc2();  
        }
        finally {
            System.out.print("Finally L2\n");
            throw new Exception();
        }
    }

    static void q2() throws Exception {
        throw new MyExc1();
    }
}

订购:

TryA L1
TryA L2
Catch L2
Finally L2
Catch L1
Finally L1        
Exception in thread "main" MyExc1 at C1.main(C1.java:30)

https://www.compilejava.net/


1
虽然这段代码片段可能是解决方案,但包括解释真的有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您提出代码建议的原因。 - Rahul Gupta

4
逻辑清晰,直到打印输出“13”结束。然后,在“main()”中,“catch(Exception i)”捕获了在“q()”中抛出的异常,并准备抛出“new MyEx2()”。 但是,在抛出异常之前,“finally”块必须先被执行。 然后,输出结果变为“132”,并且“finally”要求抛出另一个异常“new MyEx1()”。
由于方法不能抛出多个“Exception”,因此它总是抛出最新的“Exception”。换句话说,如果“catch”和“finally”块都尝试抛出“Exception”,则“catch”中的“Exception”将被“吞噬”,只有在“finally”中的异常会被抛出。
因此,在此程序中,异常“MyEx2”被忽略,而抛出了“MyEx1”。该异常从“main()”中抛出,不再被捕获,因此JVM停止运行,最终输出结果为“132Exception in thread main MyExc1”。
本质上,如果您在“try/catch”子句中有一个“finally”,则会在捕获异常后执行“finally”,但在抛出任何捕获的异常之前执行,并且只有最近的异常最终会被抛出。

3
最简单的理解方式是想象整个应用程序都有一个变量,全局持有当前异常。
Exception currentException = null;

每当抛出一个异常时,“currentException”就会被设置为该异常。当应用程序结束时,如果currentException!= null,则运行时会报告错误。

此外,finally块始终在方法退出之前运行。然后你可以将代码片段要求改为:

public class C1 {

    public static void main(String [] argv) throws Exception {
        try {
            System.out.print(1);
            q();

        }
        catch ( Exception i ) {
            // <-- currentException = Exception, as thrown by q()'s finally block
            throw( new MyExc2() ); // <-- currentException = MyExc2
        }
        finally {
             // <-- currentException = MyExc2, thrown from main()'s catch block
            System.out.print(2);
            throw( new MyExc1() ); // <-- currentException = MyExc1
        }

    }  // <-- At application exit, currentException = MyExc1, from main()'s finally block. Java now dumps that to the console.

    static void q() throws Exception {
        try {
            throw( new MyExc1() ); // <-- currentException = MyExc1
        }
        catch( Exception y ) {
           // <-- currentException = null, because the exception is caught and not rethrown
        }
        finally {
            System.out.print(3);
            throw( new Exception() ); // <-- currentException = Exception
        }
    }
}

应用程序执行的顺序是:
main()
{
  try
    q()
    {
      try
      catch
      finally
    }
  catch
  finally
}

1
众所周知,finally块在try和catch之后执行,并且始终被执行......但是正如你所看到的有时候会有一些棘手的情况,请查看下面的代码片段,你会发现return和throw语句并不总是按照我们期望的顺序执行。
干杯。
/////////////Return dont always return///////

try{

    return "In Try";

}

finally{

    return "In Finally";

}

////////////////////////////////////////////


////////////////////////////////////////////    
while(true) { 

    try {

        return "In try";

   } 

   finally{

        break;     

    }          
}              
return "Out of try";      
///////////////////////////////////////////


///////////////////////////////////////////////////

while (true) {     

    try {            

        return "In try";    

     } 
     finally {   

         continue;  

     }                         
}
//////////////////////////////////////////////////

/////////////////Throw dont always throw/////////

try {

    throw new RuntimeException();

} 
finally {

    return "Ouuuups no throw!";

}
////////////////////////////////////////////////// 

0

我认为你只需要走 finally 块:

  1. 打印 "1"。
  2. q 中的 finally 打印 "3"。
  3. main 中的 finally 打印 "2"。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接