社交媒体

try-catch-finally 执行顺序

try-catch-finally 执行顺序

钩子:一道题,测出你的水平

static int test() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

调用 test() 返回几?先把答案默念一遍,再看下去——这道题在面试里筛掉过无数人。

核心:执行顺序铁律

先看基础版顺序。没有异常:try → finally;有异常且接住了:try(出事那行中断)→ catch → finally。

try {
    System.out.println("try: 开工");
    int r = 10 / 0;              // 这行之后 try 中断
    return 1;                    // 到不了
} catch (ArithmeticException e) {
    System.out.println("catch: 接住 " + e.getMessage());
    return 2;
} finally {
    System.out.println("finally: 无论出没出事,我都执行");
}

输出:try 的前半句 → catch → finally。

finally 是"临走前必办的手续"——不管 try 是正常走完、被 catch 接住、甚至里面已经 return 了,它都执行。所以关流、解锁、还连接这类"走之前必须做的事",写在 finally 里。

27-执行顺序流程图.png

正题:return 拦不住 finally

把开头那道题换个温和的版本:

static int returnInTry() {
    try {
        return 1;                 // 返回值 1 被"记住",但先不出方法
    } finally {
        System.out.println("我还是执行了");   // 必打印
    }
}

return 已经出发,finally 依然执行——Java 的规矩是:return 时先把返回值"揣进兜里",走完 finally 才真正出门。

那开头那道题呢?finally 里也写了 return,兜里的 1 会被换成 2——返回 2。

更狠的是:如果 try 里抛了异常,finally 的 return 会把异常一起吞掉——调用方拿不到异常、看不到报错,以为一切正常。线上排查这种问题,能查一宿。

所以铁律是:finally 只做清理(关流、解锁),不写 return,不抛异常。

坑点:finally 里改返回值,生效吗?

分两种,规则很"反直觉":

// 基本类型:不生效
static int changePrimitive() {
    int result = 10;
    try {
        return result;        // 10 被复印一份揣兜里
    } finally {
        result = 99;          // 改的是复印件
    }
}                             // 返回 10

// 对象:内容生效
static Box changeBox() {
    Box box = new Box();
    try {
        return box;           // 记住的是"地址"
    } finally {
        box.buffer += "(改过)";   // 顺着地址改了真身
    }
}                             // 调用方看到改动

本质就是第 13 篇讲的:基本类型赋值是复印,对象赋值是传地址。return 揣进兜里的,一个是要值,一个是要地址。

速查表

场景 执行顺序 / 结果
无异常 try → finally
有异常且接住 try(中断处)→ catch → finally
try 里 return finally 仍执行,之后才真正返回
finally 里 return 覆盖 try 的返回值,还可能吞异常(禁写)
finally 改基本类型 不影响返回值(复印件)
finally 改对象内容 生效(同一地址)
finally 何时跳过 仅 System.exit / JVM 崩溃

本文示例代码均已本地实测通过,可直接运行。
对应文件:FinallyDemo.java


橙码酱,分享更有用的技术知识,助你成为更优秀的开发者。