1. 概述

本文深入解析 Java 中的 finally 关键字,重点讲解它在异常处理机制中的作用,尤其是在 try/catch 结构中的使用方式。

虽然 finally 的设计初衷是确保某些代码一定会执行,但我们也必须清楚:在某些极端情况下,JVM 并不会执行 finally 块。这在实际开发中容易成为“踩坑”点。

此外,我们还会剖析几个典型的陷阱场景,避免写出“看似正确、实则诡异”的代码。

2. 什么是 finally?

finally 是与 try 配套使用的关键字,用于定义一个无论是否发生异常都会执行的代码块。它的执行时机在 trycatch(如果存在)之后、方法返回之前。

✅ 核心特性:

  • 异常抛出?执行 finally
  • 异常被捕获?执行 finally
  • 正常执行?执行 finally
  • 方法提前 return?先执行 finally 再返回。

2.1. 简单示例

看一个典型的 try-catch-finally 结构:

try {
    System.out.println("The count is " + Integer.parseInt(count));
} catch (NumberFormatException e) {
    System.out.println("No count");
} finally {
    System.out.println("In finally");
}

无论 count 是否能解析为整数,finally 块中的 "In finally" 都会被打印。

2.2. 没有 catch 的 finally

finally 可以单独与 try 搭配使用,无需 catch

try {
    System.out.println("Inside try");
} finally {
    System.out.println("Inside finally");
}

输出结果:

Inside try
Inside finally

这种写法虽然少见,但语法上完全合法。

2.3. finally 的典型用途

最常见的用途是资源清理,比如:

  • 关闭文件流 ✅
  • 释放数据库连接 ✅
  • 关闭网络 socket ✅

因为这些操作必须执行,否则容易导致资源泄漏。

⚠️ 注意:现代 Java 更推荐使用 try-with-resources 语法自动管理资源,代码更简洁且不易出错。finally 手动清理的方式正在逐渐被替代。

3. finally 何时执行?

我们通过几种典型场景来验证 finally 的执行时机。

3.1. 未抛出异常

try 正常执行完毕,finally 仍会执行:

try {
    System.out.println("Inside try");
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try
Inside finally

3.2. 抛出异常但未被捕获

即使异常未被 catch 处理,finally 依然会执行:

try {
    System.out.println("Inside try");
    throw new Exception();
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try
Inside finally
Exception in thread "main" java.lang.Exception

✅ 说明:finally 在异常向上抛出前执行。

3.3. 异常被 catch 处理

异常被捕获后,finally 仍然执行:

try {
    System.out.println("Inside try");
    throw new Exception();
} catch (Exception e) {
    System.out.println("Inside catch");
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try
Inside catch
Inside finally

执行顺序:trycatchfinally

3.4. try 块中 return

即使 try 中有 returnfinally 也会先执行:

try {
    System.out.println("Inside try");
    return "from try";
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try
Inside finally

⚠️ 注意:return "from try" 的值会被暂存,finally 执行完后再真正返回。

3.5. catch 块中 return

同理,catch 中的 return 也不会跳过 finally

try {
    System.out.println("Inside try");
    throw new Exception();
} catch (Exception e) {
    System.out.println("Inside catch");
    return "from catch";
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try
Inside catch
Inside finally

4. finally 何时不执行?

虽然 finally 被设计为“必执行”,但以下几种情况会导致它被跳过。

4.1. 调用 System.exit

调用 System.exit() 会立即终止 JVM,finally 不会执行:

try {
    System.out.println("Inside try");
    System.exit(1);
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try

✅ 原因:JVM 进程直接退出,不再执行任何 Java 代码。

4.2. 调用 Runtime.halt

Runtime.halt()System.exit 更暴力,直接终止 JVM,不触发任何清理:

try {
    System.out.println("Inside try");
    Runtime.getRuntime().halt(1);
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try

⚠️ 与 System.exit 不同,halt 不会触发 shutdown hooks。

4.3. 守护线程(Daemon Thread)提前退出

如果 try/finally 在守护线程中运行,而主线程提前结束,JVM 不会等待守护线程的 finally

Runnable runnable = () -> {
    try {
        System.out.println("Inside try");
    } finally {
        try {
            Thread.sleep(1000);
            System.out.println("Inside finally");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};
Thread regular = new Thread(runnable);
Thread daemon = new Thread(runnable);
daemon.setDaemon(true);
regular.start();
Thread.sleep(300);
daemon.start();

输出可能为:

Inside try
Inside try
Inside finally

⚠️ 踩坑点:守护线程的 finally 可能不执行,因此不要在守护线程中做关键资源清理

4.4. JVM 进入无限循环

如果 trycatch 中有死循环,finally 永远不会被执行:

try {
    System.out.println("Inside try");
    while (true) {
    }
} finally {
    System.out.println("Inside finally");
}

输出:

Inside try

✅ 这不是 finally 的问题,而是代码逻辑缺陷。

5. 常见陷阱

虽然语法允许,但以下做法是强烈不推荐的,容易引发难以排查的问题。

5.1. finally 中 return 忽略异常

finally 中的 return 会吞掉未捕获的异常:

try {
    System.out.println("Inside try");
    throw new RuntimeException();
} finally {
    System.out.println("Inside finally");
    return "from finally";
}

❌ 结果:RuntimeException 被忽略,方法正常返回 "from finally"
⚠️ 这会让调用方误以为操作成功,极难调试。

5.2. finally 中 return 覆盖 try/catch 的返回值

finallyreturn 会覆盖前面所有的 return

try {
    System.out.println("Inside try");
    return "from try";
} finally {
    System.out.println("Inside finally");
    return "from finally";
}

❌ 结果:方法永远返回 "from finally""from try" 被丢弃。
✅ 建议:永远不要在 finally 中写 return

5.3. finally 中抛出异常,覆盖原有结果

finally 抛出异常会中断原有流程:

try {
    System.out.println("Inside try");
    return "from try";
} finally {
    throw new RuntimeException();
}

❌ 结果:方法不会返回任何值,而是抛出 RuntimeException
⚠️ 实际场景:清理代码(如 close())抛出异常,导致原业务异常被掩盖。

✅ 最佳实践:在 finally 中处理异常时,使用 try-catch 包裹清理逻辑,避免抛出:

finally {
    if (resource != null) {
        try {
            resource.close();
        } catch (IOException e) {
            // 记录日志,但不抛出
            System.err.println("Failed to close resource: " + e.getMessage());
        }
    }
}

6. 总结

finally 是 Java 异常处理的重要组成部分,确保关键代码(如资源释放)得以执行。但在以下情况不会执行:

  • System.exit()Runtime.halt()
  • 守护线程被强制终止
  • 无限循环阻塞执行流

⚠️ 最大陷阱:避免在 finally 中使用 return 或抛出异常,否则会掩盖真正的错误或返回值,导致逻辑混乱。

现代 Java 项目应优先使用 try-with-resources 管理资源,代码更安全简洁。只有在无法使用该语法时,才手动使用 finally 进行清理。

示例代码已上传至 GitHub:https://github.com/dev-tutorials/java-finally-demo


原始标题:Guide to the Java finally Keyword