你真的了解Java中的finally吗?

本文深入探讨Java中finally块的执行条件,包括不会执行的特殊情况及如何在return语句前执行。通过多个示例代码解析finally的工作原理,强调其在异常处理中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

序章

首先抛出一个问题:Java中的finally一定会执行吗?

很多人都认为finally是一定会执行的,其中包括一些经验老到的程序员。可惜并不像大多数人所想的那样,这个问题的答案是否定的

接下来带大家一同探讨这个问题。

01 什么情况下finally不会被执行?

例 1:

    @Test
    void test1() {
        try {
            System.out.println("try");
            System.exit(0);//退出程序
        }catch (Exception e){
            System.out.println("catch");
        }finally {
            System.out.println("finally");
        }
    }

执行结果:

try

finally语句块没有被执行,为什么呢?因为我们在语句块中执行了System.exit(0) ,终止了jvm的运行。有人有疑问了,一般情况下也不会有人在程序中调用System.exit(0) 的,那么我不在程序中调用此方法,finally语句块就一定会执行吗?

答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行

02 什么情况下finally会被执行?

例 2:

    @Test
    void test2() {
        try {
            System.out.println("try");
            return;
        }finally {
            System.out.println("finally");
        }
    }

执行结果:

try
finally

此例说明 finally 语句块在 try 语句块中的 return 语句之前执行

例 3:

@Test
    void test3() {
        System.out.println(test33());
    }
    public String test33(){
        try {
            System.out.println("try");
            int i=1/0;
            return "a";
        }catch (Exception e){
            System.out.println("catch");
            return "b";
        }finally {
            System.out.println("finally");
        }
    }

执行结果:

try
catch
finally
b

说明了 finally 语句块在 catch 语句块中的 return 语句之前执行

从例2和例3我们可以看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。

接下来

例 4:

    @Test
    public void test4(){
        System.out.println(getValue());
    }
    public int getValue(){
        try {
            return 1;
        }finally {
            return 2;
        }
    }

执行结果:

2

例5:

    @Test
    public void test5(){
        System.out.println(hello());
    }
    public String hello(){
        String s = "hello";
        try {
            return s;
        }finally {
            s = s + " finally";
        }
    }

执行结果:

hello

利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。由此,可以很容易理解例4的结果是2;

那么例5的结果为什么不是hello finally,而是hello呢?

实际上,Java 虚拟机会把 finally 语句块作为 subroutine直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

那么为了切实的看到try-finally执行,我们把例5的字节码文件拿出来分析

public void test5();
  Code:
     0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;     	 	//--PrintStream压入栈顶
     3: aload_0																							//--将引用类型本地变量推送至栈顶0的位置
     4: invokevirtual #3                  // Method hello:()Ljava/lang/String;							//--调用实例方法hello()
     7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V		//--调用方法println()
    10: return																							//--从当前方法返回void

public java.lang.String hello();
  Code:
     0: ldc           #5                  // String hello													//--将常量hello从常量池中推送至栈顶
     2: astore_1																							//--将引用类型从栈顶拉到本地变量1的位置
     3: aload_1																							//--将引用类型本地变量推送至栈顶1的位置
     4: astore_2																							//--将引用类型从栈顶拉到本地变量2的位置
     5: new           #6                  // class java/lang/StringBuilder								//--创建一个对象StringBuilder
     8: dup																								//--复制栈顶的值重新压入栈顶
     9: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V					//--调用超类构造方法StringBuilder()
    12: aload_1																							//--将引用类型本地变量推送至栈顶1的位置
    13: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;		//--调用方法append()
    16: ldc           #9                  // String  finally																			//--finally从常量池推送至栈顶
    18: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;		//--调用方法append()
    21: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;								//--调用方法toString()
    24: astore_1
    25: aload_2
    26: areturn																							//--返回引用类型
    27: astore_3
    28: new           #6                  // class java/lang/StringBuilder
    31: dup
    32: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
    35: aload_1
    36: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    39: ldc           #9                  // String  finally
    41: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    44: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    47: astore_1
    48: aload_3
    49: athrow																							//--将栈顶的异常抛出
  Exception table:
     from    to  target type
         3     5    27   any																				//--从3到5的这段指令出现异常,则由27开始的指令来处理

可以看到在执行16步之前,先执行了0-4步,将hello存入本地变量表2位置;

没有异常时:

执行finally16-24,拼接成"hello finally",存入本地变量表1的位置;接着执行25-26,取出变量表2位置的"hello"返回给调用者;

有异常时:

执行finally39-47,拼接成"hello finally",存入本地变量表1的位置;如果有异常会把异常存到本地变量表3的位置,然后执行48-49,取出异常抛出。

所以结果不是hello finally,而是hello。

03 练习

例5​:

@Test
    public void test6(){
        System.out.println(test66());
    }
    public int test66(){
        int i = 1;
        try {
            i = 5;
        } finally {
            i++;
            return i;
        }
    }

执行结果:

6

例7:

@Test
    public void test7() {
        System.out.println(test76());
    }public String test76() {
        try {
            System.out.println("try");
            return test77();
        } finally {
            System.out.println("finally");
        }
    }public String test77() {
        System.out.println("print test77");
        return "return test77";
    }

执行结果:

try
print test77
finally
return test77

例8:

    @Test
    public void test8(){
        System.out.println(test88());
    }
    public int test88(){
        try {
            int a=1/0;
        } catch (Exception e) {
            System.out.println("catch");
            return 11;
        } finally {
            System.out.println("finally");
            return 22;
        }
    }

执行结果:

catch
finally
22​

例9​:

@Test
    public void test10(){
        try {
            int a=1/0;
        }catch (Exception e){
            throw new RuntimeException("除0异常");
        }finally {
            System.out.println("finally");
            return;
        }
    }

通过练习之后,小伙伴们是不是掌握finally的用法了。

​总结一下:

1、除了前面1中讨论的情况finally 块必然执行,不论发生异常与否,也不论在 finally 之前是否有return。

2、finally 执行总是在return之前​。

3、finally 中若有return,则try或者catch中的return​就不会执行了。所以要注意不要写出例9代码,防止出现无法察觉的错误​。

<think>嗯,用户问的是Javafinally、break和continue的用法以及它们之间的相互关系。首先,我需要回顾一下这三个关键字的基本作用,然后想想它们在不同情况下的交互行为。 首先,finally通常是和try-catch块一起使用的,不管有没有异常,finally里的代码都会执行。这一点我记得没错,比如在try块里打开了一个文件,不管有没有异常,finally里都应该关闭它,确保资源释放。 然后是break和continue,这两个都是用在循环结构里的。break用于立即跳出当前循环,而continue则是跳过当前循环的剩余代码,进入下一次循环。比如在for或者while循环中,遇到break会直接终止整个循环,而continue只是跳过当前迭代。 接下来,用户可能想知道当这三个关键字出现在同一个代码块中时,会发生什么。例如,如果在循环里有一个try块,里面使用了break或者continue,finally里的代码会不会执行?比如,在循环里有一个try,里面调用了break,这时候循环会终止,但finally里的代码是否会在循环终止前执行? 根据我的记忆,即使在try块中使用break或continue,finally块仍然会执行。比如,在下面的代码中: ```java for (int i = 0; i < 5; i++) { try { if (i == 2) { break; } System.out.println(i); } finally { System.out.println("Finally executed for i=" + i); } } ``` 当i等于2时,break会触发,但finally块会在循环终止前执行,所以输出应该包括i=2时的finally语句。 类似的情况,如果使用continue,比如: ```java for (int i = 0; i < 5; i++) { try { if (i == 2) { continue; } System.out.println(i); } finally { System.out.println("Finally executed for i=" + i); } } ``` 当i等于2时,continue会跳过当前迭代的剩余代码,但finally仍然执行,所以输出中会有i=2的finally,但不会打印i的值。 另外,用户可能还想知道,如果在finally块中使用break或continue会有什么问题。比如,在finally里写break或continue是否合法?比如,在下面的例子中: ```java for (int i = 0; i < 5; i++) { try { // 一些代码 } finally { if (i == 3) { break; // 这里是否允许? } } } ``` 根据Java的语法,break和continue必须位于循环内部,如果在finally块中直接使用,而finally本身不在循环内的话,就会导致编译错误。但如果在循环内部的try的finally里,是否允许呢?比如,假设整个try-finally结构在循环内部,那么finally里的break是可以的,因为它属于循环的一部分。不过,这样的写法可能会导致逻辑混乱,因为即使try块中有break,finally里的break仍然会执行,从而导致可能的控制流改变。 比如,在下面的例子: ```java for (int i = 0; i < 5; i++) { try { if (i == 1) { break; } } finally { if (i == 2) { break; } } } ``` 这里,当i=1时,try块里的break会被触发,但接着会执行finally块。在i=1的情况下,finally里的条件不满足,所以不会执行break,循环终止。而当i=2时,try块里没有break,执行finally块,这里的break会导致循环终止。这可能是一个需要注意的地方,因为finally中的break可能会覆盖之前try块中的控制流。 还有,如果在finally块中使用了return,会覆盖之前的break或continue吗?比如,在方法中的循环里,如果在finally里return,会导致方法直接返回,从而中断循环,甚至忽略之前的控制语句。 不过用户的问题主要集中在finally、break、continue的交互,所以我需要重点说明当这些关键字出现在同一上下文中时的执行顺序和影响。 总结一下: 1. finally中的代码总是在try或catch块之后执行,无论是否有异常,或者是否有break/continue。 2. 在try块中使用break或continue时,会先执行finally中的代码,然后再执行break或continue。 3. 在finally块中使用break或continue是允许的,但必须确保它们在循环内部,否则会导致编译错误。 4. 如果在finally块中使用了控制语句(如break、continue、return),它们会覆盖之前的控制流,可能导致意外的行为。 此外,用户可能需要一些代码示例来直观理解这些行为。例如,展示在try块中使用break,然后在finally中执行代码的情况,或者finally中使用continue的例子。 还要注意,如果在try块中有return语句,finally也会在return之前执行。不过用户的问题中没有提到return,但可能相关的例子可以帮助理解。 最后,用户可能需要了解这些交互可能导致的常见错误,比如在finally中修改控制流可能掩盖try或catch中的异常,或者导致资源未正确释放等问题。</think>### Java中`finally`、`break`和`continue`的用法及相互关系 #### 1. **`finally`的作用** - **功能**:`finally`是异常处理结构`try-catch-finally`的一部分,无论是否发生异常或是否被捕获,`finally`代码块**必然执行**。 - **典型场景**:用于资源释放(如关闭文件、数据库连接)[^1]。 - **示例**: ```java try { // 可能抛出异常的代码 } catch (Exception e) { // 异常处理 } finally { System.out.println("无论是否异常,都会执行"); } ``` #### 2. **`break`的作用** - **功能**:立即**终止当前循环**(`for`、`while`、`do-while`)或`switch`语句。 - **示例**: ```java for (int i = 0; i < 5; i++) { if (i == 3) { break; // 循环在i=3时终止 } System.out.println(i); // 输出0,1,2 } ``` #### 3. **`continue`的作用** - **功能**:跳过当前循环的剩余代码,**直接进入下一次循环迭代**。 - **示例**: ```java for (int i = 0; i < 5; i++) { if (i == 2) { continue; // 跳过i=2时的后续代码 } System.out.println(i); // 输出0,1,3,4 } ``` --- #### 4. **三者的交互行为** ##### 场景1:**`finally`与`break`/`continue`** - **规则**:在`try`或`catch`块中使用`break`或`continue`时,`finally`仍会优先执行。 - **示例**: ```java for (int i = 0; i < 3; i++) { try { if (i == 1) { break; // 触发循环终止 } } finally { System.out.println("Finally: " + i); // 输出i=0,1 } } ``` **结果**: ``` Finally: 0 Finally: 1 ``` ##### 场景2:**`finally`中修改控制流** - **规则**:若`finally`中包含`break`或`continue`,它会**覆盖原有控制流**。 - **示例**: ```java for (int i = 0; i < 3; i++) { try { if (i == 1) { continue; // 触发跳过i=1 } } finally { if (i == 1) { break; // 覆盖continue,直接终止循环 } System.out.println(i); // 输出0 } } ``` **结果**:仅输出`0`(循环在i=1时被`finally`中的`break`终止)。 --- #### 5. **关键注意事项** 1. **编译错误**:若`finally`中的`break`或`continue`不在循环内部,会导致编译错误。 2. **逻辑陷阱**:`finally`中修改控制流可能掩盖异常或导致资源未释放。 3. **执行顺序**:`finally`执行优先级高于`break`/`continue`/`return`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值