MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java finally块执行时机解析

2021-12-165.6k 阅读

Java finally 块概述

在Java编程语言中,try - catch - finally 结构是异常处理机制的重要组成部分。finally 块通常与 try 块一起使用,无论 try 块中是否发生异常,finally 块中的代码都有机会执行。这为我们提供了一种可靠的方式来执行那些无论程序执行路径如何都必须执行的代码,比如关闭文件句柄、释放数据库连接等资源清理操作。

finally 块的语法结构非常直观,它紧跟在 try 块和可选的 catch 块之后。例如:

try {
    // 可能会抛出异常的代码
    int result = 10 / 0;
    System.out.println("这行代码不会被执行,因为前面抛出了异常");
} catch (ArithmeticException e) {
    System.out.println("捕获到算术异常: " + e.getMessage());
} finally {
    System.out.println("finally块中的代码总是会执行");
}

在上述代码中,try 块尝试执行一个除法操作,由于除数为零,会抛出 ArithmeticException 异常。catch 块捕获到该异常并打印出异常信息,最后 finally 块中的代码被执行,输出 “finally块中的代码总是会执行”。

正常执行情况下 finally 块的执行

try 块中的代码正常执行完毕,没有抛出任何异常时,finally 块会在 try 块执行结束后立即执行。例如:

try {
    int num = 10;
    int result = num + 5;
    System.out.println("计算结果: " + result);
} finally {
    System.out.println("finally块在try块正常执行完毕后执行");
}

在这段代码中,try 块进行了简单的加法运算并打印结果,没有异常发生。程序会先执行 try 块中的代码,输出 “计算结果: 15”,然后立即执行 finally 块中的代码,输出 “finally块在try块正常执行完毕后执行”。

异常发生时 finally 块的执行

  1. 异常被捕获时try 块中抛出异常,并且该异常被对应的 catch 块捕获时,catch 块中的代码会先执行,然后执行 finally 块。例如:
try {
    int[] arr = new int[5];
    int value = arr[10]; // 访问越界,抛出ArrayIndexOutOfBoundsException
    System.out.println("这行代码不会被执行");
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("捕获到数组越界异常: " + e.getMessage());
} finally {
    System.out.println("finally块在异常被捕获处理后执行");
}

在这个例子中,try 块尝试访问数组越界的位置,抛出 ArrayIndexOutOfBoundsException 异常。catch 块捕获到该异常并打印异常信息,随后 finally 块中的代码被执行,输出 “finally块在异常被捕获处理后执行”。

  1. 异常未被捕获时 如果 try 块中抛出的异常没有被当前 try - catch 结构中的 catch 块捕获,finally 块仍然会执行,然后异常会继续向上层调用栈传播。例如:
public class UncaughtExceptionExample {
    public static void main(String[] args) {
        try {
            methodThatThrowsException();
        } finally {
            System.out.println("finally块在异常未被当前捕获时执行");
        }
    }

    static void methodThatThrowsException() {
        throw new RuntimeException("这是一个未被捕获的运行时异常");
    }
}

在上述代码中,methodThatThrowsException 方法抛出一个 RuntimeException,并且这个异常没有在 try - catch 结构中被捕获。finally 块会先执行,输出 “finally块在异常未被当前捕获时执行”,然后异常向上传播,最终导致程序终止,并在控制台打印出异常堆栈跟踪信息。

与 return 语句结合时 finally 块的执行

  1. try 块中有 return 语句try 块中包含 return 语句时,finally 块会在 return 语句执行之前执行,但 finally 块执行完毕后,try 块中的 return 语句仍然会执行。例如:
public class ReturnInTryExample {
    public static int test() {
        try {
            return 10;
        } finally {
            System.out.println("finally块在try块的return之前执行");
        }
    }

    public static void main(String[] args) {
        int result = test();
        System.out.println("返回结果: " + result);
    }
}

在这个例子中,test 方法的 try 块中有一个 return 语句。当执行到 return 语句时,会先执行 finally 块,输出 “finally块在try块的return之前执行”,然后 try 块中的 return 语句继续执行,返回值为 10。main 方法中接收返回值并打印 “返回结果: 10”。

  1. catch 块中有 return 语句 如果 catch 块中包含 return 语句,finally 块同样会在 return 语句执行之前执行。例如:
public class ReturnInCatchExample {
    public static int test() {
        try {
            int result = 10 / 0; // 抛出ArithmeticException
            return result;
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常");
            return -1;
        } finally {
            System.out.println("finally块在catch块的return之前执行");
        }
    }

    public static void main(String[] args) {
        int result = test();
        System.out.println("返回结果: " + result);
    }
}

在这个代码中,try 块抛出 ArithmeticException 异常,catch 块捕获该异常并打印信息,然后 catch 块中有一个 return 语句。在执行 return 语句之前,finally 块会先执行,输出 “finally块在catch块的return之前执行”,接着 catch 块中的 return 语句执行,返回 -1,main 方法打印 “返回结果: -1”。

  1. finally 块对返回值的影响finally 块中修改返回值的情况比较特殊。当 trycatch 块中有 return 语句时,finally 块可以访问到 return 语句中的返回值,但对这个返回值的修改并不总是能生效。对于基本数据类型,finally 块对返回值的修改不会影响最终返回结果。例如:
public class FinallyReturnValueExample {
    public static int test() {
        int num = 10;
        try {
            return num;
        } finally {
            num = 20;
            System.out.println("finally块中修改num为20");
        }
    }

    public static void main(String[] args) {
        int result = test();
        System.out.println("返回结果: " + result);
    }
}

在上述代码中,try 块返回 num 的值 10。虽然 finally 块中将 num 修改为 20,但最终返回结果仍然是 10,因为基本数据类型在 return 语句执行时已经确定了返回值,finally 块对其修改无效。

然而,对于引用数据类型,情况会有所不同。如果 finally 块修改了引用数据类型的属性,这个修改会反映到最终返回的对象上。例如:

class MyClass {
    int value;

    public MyClass(int value) {
        this.value = value;
    }
}

public class FinallyReferenceReturnValueExample {
    public static MyClass test() {
        MyClass obj = new MyClass(10);
        try {
            return obj;
        } finally {
            obj.value = 20;
            System.out.println("finally块中修改obj的value为20");
        }
    }

    public static void main(String[] args) {
        MyClass result = test();
        System.out.println("返回对象的value: " + result.value);
    }
}

在这个例子中,test 方法返回一个 MyClass 对象。finally 块修改了该对象的 value 属性。最终返回的对象 resultvalue 属性值为 20,因为 finally 块对引用数据类型对象属性的修改是有效的。

异常在 finally 块中抛出的情况

  1. finally 块抛出新异常 如果 finally 块中抛出了新的异常,这个异常会覆盖 trycatch 块中原本抛出的异常(如果有的话)。例如:
public class NewExceptionInFinallyExample {
    public static void main(String[] args) {
        try {
            try {
                int result = 10 / 0; // 抛出ArithmeticException
            } finally {
                throw new RuntimeException("finally块中抛出的运行时异常");
            }
        } catch (Exception e) {
            System.out.println("捕获到异常: " + e.getMessage());
        }
    }
}

在这段代码中,try 块内部抛出了 ArithmeticException,但 finally 块又抛出了 RuntimeException。最终外层 catch 块捕获到的是 finally 块中抛出的 RuntimeException,输出 “捕获到异常: finally块中抛出的运行时异常”。

  1. finally 块中异常与 try - catch 块中异常的关系finally 块抛出异常时,trycatch 块中原本抛出的异常会被抑制(Java 7 及以上)。可以通过 Throwable.getSuppressed() 方法获取被抑制的异常。例如:
public class SuppressedExceptionExample {
    public static void main(String[] args) {
        try {
            try {
                throw new IOException("try块中抛出的IOException");
            } finally {
                throw new RuntimeException("finally块中抛出的运行时异常");
            }
        } catch (Exception e) {
            System.out.println("捕获到异常: " + e.getMessage());
            Throwable[] suppressedExceptions = e.getSuppressed();
            for (Throwable suppressed : suppressedExceptions) {
                System.out.println("被抑制的异常: " + suppressed.getMessage());
            }
        }
    }
}

在这个例子中,try 块抛出 IOExceptionfinally 块抛出 RuntimeException。外层 catch 块捕获到 RuntimeException,通过 getSuppressed() 方法可以获取到被抑制的 IOException,并打印出相关信息。

线程相关情况下 finally 块的执行

  1. 线程中断 当线程在 try 块执行过程中被中断,finally 块仍然会执行。例如:
public class ThreadInterruptedExceptionExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("线程正在运行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("线程被中断", e);
                    }
                }
            } finally {
                System.out.println("finally块在线程中断时执行");
            }
        });

        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

在这个代码中,线程在 try 块中循环运行,并通过 Thread.sleep 方法暂停执行。主线程在 3 秒后中断该线程。当线程被中断时,catch 块捕获 InterruptedException,重新设置中断状态并抛出 RuntimeException,最后 finally 块会执行,输出 “finally块在线程中断时执行”。

  1. 线程死亡 如果线程在 try 块执行过程中由于未捕获的异常导致线程死亡,finally 块同样会执行。例如:
public class ThreadDeathExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                int result = 10 / 0; // 抛出ArithmeticException导致线程死亡
            } finally {
                System.out.println("finally块在线程因异常死亡时执行");
            }
        });

        thread.start();
    }
}

在这个例子中,线程在 try 块中执行除法操作时抛出 ArithmeticException,导致线程死亡。但在死亡之前,finally 块会执行,输出 “finally块在线程因异常死亡时执行”。

嵌套 try - catch - finally 结构中 finally 块的执行

当存在嵌套的 try - catch - finally 结构时,每个 finally 块都会按照其所在层次的执行规则执行。例如:

public class NestedTryCatchFinallyExample {
    public static void main(String[] args) {
        try {
            try {
                int result = 10 / 0; // 内层try块抛出ArithmeticException
            } catch (ArithmeticException e) {
                System.out.println("内层catch块捕获到算术异常");
            } finally {
                System.out.println("内层finally块执行");
            }
        } catch (Exception e) {
            System.out.println("外层catch块捕获到异常");
        } finally {
            System.out.println("外层finally块执行");
        }
    }
}

在这个代码中,内层 try 块抛出 ArithmeticException,内层 catch 块捕获并处理该异常,然后内层 finally 块执行,输出 “内层finally块执行”。由于内层异常已被捕获,外层 catch 块不会捕获到异常,但外层 finally 块仍然会执行,输出 “外层finally块执行”。

总结

finally 块在Java异常处理机制中扮演着至关重要的角色,它确保了无论程序执行过程中是否发生异常,某些关键代码都能得到执行。理解 finally 块在不同情况下的执行时机,包括正常执行、异常发生、与 return 语句结合、异常在 finally 块中抛出、线程相关以及嵌套结构等情况,对于编写健壮、可靠的Java程序至关重要。通过合理运用 finally 块,我们可以有效地管理资源,避免资源泄漏等问题,提高程序的稳定性和可靠性。在实际开发中,我们应根据具体的业务需求,谨慎地设计 try - catch - finally 结构,确保程序在各种情况下都能正确运行。