在 Java 中,无论其前面的 try
代码块是否抛出异常,finally
都会执行到,所以它通常被用来关闭资源或进行清理操作。但是确实是存在一些特殊情况,导致 finally
是不会执行到的。
详解
1、在 try 语句之前 return 或者抛出异常
@Test
public void finallyTest() {
int i = 1 / 0;
try {
//....
} finally {
System.out.println("执行finally");
}
}
finally
语句被执行的必要而非充分条件是相应的 try 语句一定被执行到。
2、程序所在的线程死亡
序所在的线程在进入 finally
代码块之前死亡,例如调用 System.exit(0)
,finally
代码块不会被执行。
@Test
public void finallyTest() {
try {
System.out.println("执行 try 代码块");
System.exit(0);
} finally {
System.out.println("执行finally");
}
}
System.exit(0)
是终止Java虚拟机的,JVM都停止了,自然所有的程序就都结束了,当然finally
语句也就不会被执行了。
3、守护线程
如果 finally
代码块是在守护线程中,并且所有的非守护线程都已经退出,那么JVM可能在守护线程执行 finally
代码块之前退出。
public class FinallyTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "--执行守护线程");
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
System.out.println(Thread.currentThread().getName() + "--执行守护线程 finally 代码块");
}
});
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-退出");
}
}
结果:
main 线程退出后,JVM 就会退出了,finally
就执行不到了。
扩展
下面我们通过一些示例来看看 try
、catch
、finally
代码块的执行顺序。
示例一
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
return 1;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
}
}
}
执行结果:
finally
代码块在 try
代码块 return 语句前执行。
示例二
基于上面例子改造下:
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
int i = 1 / 0;
return 1;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
}
}
}
执行结果:
finally
代码块在 catch
代码块 return 语句前执行。
示例三
就要示例一,我们再改造下:
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
return 1;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
return 2;
}
}
}
执行结果:
你会看到 finallyTest()
返回的是 finally
代码块的 2。这是为什么呢?这里先不解释,继续看你就会慢慢明白了。
示例四
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
int i = 1;
try {
System.out.println("try 代码块被执行!");
return i;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
i++;
System.out.println("finally 代码块中的 i=" + i);
}
}
}
执行结果
finally
代码中的 i = 2 ,但是返回的 1。是不是有点儿奇怪,前面介绍了 finally
代码块是在 return 之前执行的,但是在 finally
中 i = 2,为什么返回的还是 1 呢?
当程序执行到 try
代码块的 return
语句时,返回值会被存储起来,但在实际返回给调用者之前,finally
代码块会被执行。如果 finally
代码块中也有一个 return
语句,那么它将覆盖 try
代码块中的返回值,并且是最终的返回结果。
示例五
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
return tryTest();
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
}
}
public static int tryTest() {
System.out.println("tryTest 代码块被执行!");
return 3;
}
}
执行结果
这结果有没有出乎你的意料?前面不是提过,finally
里面的代码块不是比 try
代码库里面的 return 先执行吗?怎么会出现 tryTest()
比 finally
还先执行呢?原因如下:
在Java中,当一个try
代码块包含一个return
语句时,这个return
语句会“先执行”,但并不是立即返回。这里的“先执行”指的是确定返回值的过程会在进入finally
代码块之前发生。然而,实际上在finally
代码块执行完毕之前,方法不会真正返回。
最后
所以 try
里面有 return
语句时,执行过程如下:
- 执行 try 代码块。
- 执行
try
里面的return
。当程序执行到try
代码块中的return
语句时,会先计算这个return
语句的返回值。例如,如果返回的是一个变量的值,那么这个值会被确定下来。所以示例5 中的tryTest()
是先于finally
执行的。 - 暂停返回:尽管
try
代码块中的return
语句已经“执行”了,但方法不会立即返回。而是转移到finally
代码块。 - 执行
finally
代码块:finally
代码块现在执行。如果finally
代码块中也有return
语句,它会覆盖try
代码块中的返回值。 - 完成方法返回:如果
finally
代码块没有新的return
语句,那么最初在try
代码块中确定的返回值将被用作方法的最终返回值。如果finally
代码块中有return
语句,那么它的返回值会成为最终结果。
所以,记住这两点即可:
try
代码块永远比finally
代码库先执行。finally
的 return 会覆盖try
的return。