【Java】Java 中的 try-catch-finally 详解

在 Java 开发中,异常处理是重要的一部分。程序在运行过程中,可能会出现各种各样的问题,例如文件不存在、网络连接失败、数组越界、空指针访问等。如果不对这些异常进行处理,程序可能会直接崩溃。

Java 提供了 try-catch-finally 机制,用来捕获并处理程序运行时可能出现的异常,从而提高程序的健壮性。

1 什么是异常?

异常,英文是 Exception,表示程序在运行过程中出现的不正常情况。

例如:

public class Demo {
    public static void main(String[] args) {
        int result = 10 / 0;
        System.out.println(result);
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero

因为整数不能除以 0,所以程序抛出了一个 ArithmeticException 异常。

如果没有异常处理,程序会在异常发生的位置直接终止。


2 try-catch-finally 的基本结构

基本语法如下:

try {
    // 可能会出现异常的代码
} catch (ExceptionType e) {
    // 捕获并处理异常
} finally {
    // 无论是否发生异常,通常都会执行的代码
}

其中:

关键字 作用
try 包裹可能出现异常的代码
catch 捕获并处理异常
finally 无论是否发生异常,通常都会执行

示例:

public class Demo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("发生了算术异常:" + e.getMessage());
        }

        System.out.println("程序继续执行");
    }
}

运行结果:

发生了算术异常:/ by zero
程序继续执行

可以看到,异常被 catch 捕获之后,程序没有崩溃,而是继续向下执行。

3.1 try 块的作用

try 块中通常放置可能发生异常的代码。

例如:

try {
    String str = null;
    System.out.println(str.length());
}

这段代码可能会抛出 NullPointerException,因为 strnull,不能调用 length() 方法。

完整写法:

public class Demo {
    public static void main(String[] args) {
        try {
            String str = null;
            System.out.println(str.length());
        } catch (NullPointerException e) {
            System.out.println("空指针异常:" + e.getMessage());
        }
    }
}

3.2 catch 块的作用

catch 用来捕获指定类型的异常。

语法:

catch (异常类型 变量名) {
    // 异常处理逻辑
}

例如:

catch (ArithmeticException e) {
    System.out.println("除数不能为 0");
}

这里的 e 是异常对象,可以通过它获取异常信息。

常用方法:

e.getMessage();      // 获取异常信息
e.printStackTrace(); // 打印异常堆栈信息
e.toString();        // 获取异常类型和异常信息

示例:

public class Demo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("getMessage(): " + e.getMessage());
            System.out.println("toString(): " + e.toString());
            e.printStackTrace();
        }
    }
}

3.3 finally 块的作用

finally 块中的代码通常用于释放资源,例如关闭文件流、关闭数据库连接、关闭网络连接等。

它的特点是:

无论是否发生异常,finally 中的代码通常都会执行。

示例:

public class Demo {
    public static void main(String[] args) {
        try {
            int result = 10 / 2;
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("发生异常");
        } finally {
            System.out.println("finally 执行了");
        }
    }
}

运行结果:

结果:5
finally 执行了

如果发生异常:

public class Demo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("发生异常:" + e.getMessage());
        } finally {
            System.out.println("finally 执行了");
        }
    }
}

运行结果:

发生异常:/ by zero
finally 执行了

4 try-catch-finally 的执行流程

4.1 没有异常时

try {
    System.out.println("try 执行");
} catch (Exception e) {
    System.out.println("catch 执行");
} finally {
    System.out.println("finally 执行");
}

运行结果:

try 执行
finally 执行

执行流程:

try -> finally -> 后续代码

4.2 出现异常并被 catch 捕获

try {
    System.out.println("try 开始");
    int result = 10 / 0;
    System.out.println("try 结束");
} catch (ArithmeticException e) {
    System.out.println("catch 执行");
} finally {
    System.out.println("finally 执行");
}

运行结果:

try 开始
catch 执行
finally 执行

注意:

int result = 10 / 0;

之后的代码不会再执行,所以:

System.out.println("try 结束");

不会输出。

执行流程:

try -> 出现异常 -> catch -> finally -> 后续代码

4.3 出现异常但没有被 catch 捕获

public class Demo {
    public static void main(String[] args) {
        try {
            String str = null;
            System.out.println(str.length());
        } catch (ArithmeticException e) {
            System.out.println("算术异常");
        } finally {
            System.out.println("finally 执行");
        }

        System.out.println("程序结束");
    }
}

运行结果:

finally 执行
Exception in thread "main" java.lang.NullPointerException

因为实际发生的是 NullPointerException,但是 catch 捕获的是 ArithmeticException,类型不匹配,所以异常没有被捕获。

不过,finally 仍然会执行。

执行流程:

try -> 出现异常 -> catch 不匹配 -> finally -> 异常继续向外抛出

5 try 后面可以跟多个 catch

一个 try 后面可以跟多个 catch,用于处理不同类型的异常。

示例:

public class Demo {
    public static void main(String[] args) {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[5]);
        } catch (ArithmeticException e) {
            System.out.println("算术异常");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组下标越界异常");
        } catch (Exception e) {
            System.out.println("其他异常");
        }
    }
}

运行结果:

数组下标越界异常

需要注意的是,多个 catch 的顺序非常重要。

子类异常必须写在前面,父类异常必须写在后面。

错误写法:

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[5]);
} catch (Exception e) {
    System.out.println("异常");
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界");
}

这段代码会编译报错,因为 Exception 是父类,已经可以捕获所有异常,后面的 ArrayIndexOutOfBoundsException 永远没有机会执行。

正确写法:

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[5]);
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界");
} catch (Exception e) {
    System.out.println("异常");
}

6 JDK 7 之后的多异常捕获

从 JDK 7 开始,Java 支持在一个 catch 中捕获多个异常。

语法:

catch (异常类型1 | 异常类型2 e) {
    // 异常处理
}

示例:

public class Demo {
    public static void main(String[] args) {
        try {
            String str = null;
            System.out.println(str.length());

            int result = 10 / 0;
            System.out.println(result);
        } catch (NullPointerException | ArithmeticException e) {
            System.out.println("发生了空指针异常或算术异常");
        }
    }
}

注意:

多异常捕获时,异常变量 e 默认是 final 的,不能重新赋值。

catch (NullPointerException | ArithmeticException e) {
    // e = new RuntimeException(); // 编译错误
}

7 finally 一定会执行吗?

通常情况下,finally 一定会执行。

例如,即使 try 中有 returnfinally 也会执行。

public class Demo {
    public static int test() {
        try {
            System.out.println("try 执行");
            return 1;
        } catch (Exception e) {
            System.out.println("catch 执行");
            return 2;
        } finally {
            System.out.println("finally 执行");
        }
    }

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

运行结果:

try 执行
finally 执行
返回值:1

极特殊的情况下 finally 不会执行,一般是那种强硬的、“不可抗力”的事件。

例如:

public class Demo {
    public static void main(String[] args) {
        try {
            System.out.println("try 执行");
            System.exit(0);
        } finally {
            System.out.println("finally 执行");
        }
    }
}

运行结果:

try 执行

因为 System.exit(0) 会直接终止 JVM,所以 finally 不会执行。

常见导致 finally 不执行的情况:

  1. 调用了 System.exit()
  2. JVM 崩溃
  3. 操作系统强制杀死进程
  4. 程序所在机器突然断电
  5. 守护线程中的 finally 还未执行,所有非守护线程已经结束

8 finally 中不要随便写 return

这是一个非常重要的坑。

如果 finally 中写了 return,它会覆盖 trycatch 中的返回值。

示例:

public class Demo {
    public static int test() {
        try {
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            return 3;
        }
    }

    public static void main(String[] args) {
        System.out.println(test());
    }
}

运行结果:

3

虽然 try 中返回了 1,但是 finally 中又返回了 3,最终结果变成了 3

因此,实际开发中不推荐在 finally 中写 return


9 finally 中修改返回变量的问题

来看一个经典面试题:

public class Demo {
    public static int test() {
        int num = 10;
        try {
            return num;
        } finally {
            num = 20;
        }
    }

    public static void main(String[] args) {
        System.out.println(test());
    }
}

运行结果:

10

为什么不是 20 ?

原因是:当执行到 return num 时,Java 会先把 num 的值保存起来,准备返回。然后执行 finally,虽然 finally 中把 num 改成了 20,但是返回值已经被保存为 10,所以最终返回 10。

再看一个引用类型的例子:

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static List<String> test() {
        List<String> list = new ArrayList<>();

        try {
            list.add("try");
            return list;
        } finally {
            list.add("finally");
        }
    }

    public static void main(String[] args) {
        System.out.println(test());
    }
}

运行结果:

[try, finally]

因为返回的是引用地址,finally 修改的是同一个对象中的内容,所以返回结果中包含了 "finally"


10 try-catch-finally 的组合形式

10.1 try-catch

try {
    // 可能出现异常的代码
} catch (Exception e) {
    // 异常处理
}

适用于只需要捕获异常,不需要释放资源的场景。


10.2 try-finally

try {
    // 可能出现异常的代码
} finally {
    // 释放资源
}

这种写法不捕获异常,只保证 finally 执行。

示例:

public class Demo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
        } finally {
            System.out.println("释放资源");
        }
    }
}

运行结果:

释放资源
Exception in thread "main" java.lang.ArithmeticException: / by zero

10.3 try-catch-finally

try {
    // 可能出现异常的代码
} catch (Exception e) {
    // 异常处理
} finally {
    // 释放资源
}

这是最完整的形式。


11 异常体系结构

Java 中异常的顶层父类是 Throwable

大致结构如下:

Throwable
├── Error
└── Exception
    ├── RuntimeException
    │   ├── NullPointerException
    │   ├── ArithmeticException
    │   ├── ArrayIndexOutOfBoundsException
    │   └── ClassCastException
    └── IOException

说明:

类型 含义
Throwable 所有异常和错误的父类
Error 严重错误,通常程序无法处理
Exception 异常,程序可以处理
RuntimeException 运行时异常
IOException IO 异常,属于编译时异常

12 运行时异常和编译时异常

Java 中的异常大致可以分为两类:

12.1 运行时异常

运行时异常继承自 RuntimeException

常见运行时异常:

NullPointerException
ArithmeticException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException

特点:

  • 编译器不会强制要求处理
  • 通常是代码逻辑问题导致的
  • 可以通过代码优化避免

示例:

String str = null;
System.out.println(str.length());

12.2 编译时异常

编译时异常也叫受检异常,除了 RuntimeException 及其子类以外,其他 Exception 通常都是编译时异常。

例如:

IOException
SQLException
ClassNotFoundException
FileNotFoundException

特点:

  • 编译器强制要求处理
  • 要么使用 try-catch
  • 要么使用 throws 抛出

示例:

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Demo {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("test.txt");
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在");
        }
    }
}

如果不处理,会直接编译报错。


12.3 try-catch-finally 与 throws 的区别

try-catch-finally 是在当前方法中处理异常。

throws 是把异常抛给方法调用者处理。

示例:

public static void readFile() throws Exception {
    FileInputStream fis = new FileInputStream("test.txt");
}

调用者处理:

public static void main(String[] args) {
    try {
        readFile();
    } catch (Exception e) {
        System.out.println("读取文件失败");
    }
}

区别:

方式 作用
try-catch 当前方法自己处理异常
throws 当前方法不处理,交给调用者处理
finally 主要用于释放资源

12.4 finally 的典型应用:关闭资源

以前在关闭 IO 流时,经常使用 finally

示例:

import java.io.FileInputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        FileInputStream fis = null;

        try {
            fis = new FileInputStream("test.txt");

            int data = fis.read();
            System.out.println(data);
        } catch (IOException e) {
            System.out.println("IO 异常:" + e.getMessage());
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                System.out.println("关闭流失败:" + e.getMessage());
            }
        }
    }
}

这段代码中:

fis.close();

放在 finally 中,是为了保证无论读取文件是否成功,文件流都能够被关闭。


15 try-with-resources 简介

从 JDK 7 开始,Java 引入了 try-with-resources,可以自动关闭资源。

语法:

try (资源对象) {
    // 使用资源
} catch (Exception e) {
    // 异常处理
}

示例:

import java.io.FileInputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("test.txt")) {
            int data = fis.read();
            System.out.println(data);
        } catch (IOException e) {
            System.out.println("IO 异常:" + e.getMessage());
        }
    }
}

只要资源类实现了 AutoCloseableCloseable 接口,就可以使用 try-with-resources 自动关闭。

常见可自动关闭的资源:

FileInputStream
FileOutputStream
BufferedReader
BufferedWriter
Connection
Statement
ResultSet

在现代 Java 开发中,关闭资源时更推荐使用 try-with-resources


16 开发中的建议

16.1 不要捕获过大的异常范围

不推荐:

try {
    // 业务代码
} catch (Exception e) {
    e.printStackTrace();
}

虽然这样很方便,但会掩盖真实问题。

更推荐:

try {
    // 业务代码
} catch (IOException e) {
    // 处理 IO 异常
} catch (SQLException e) {
    // 处理 SQL 异常
}

16.2 不要空 catch

不推荐:

try {
    int result = 10 / 0;
} catch (Exception e) {
}

这样会导致异常被吞掉,排查问题非常困难。

推荐至少记录日志:

try {
    int result = 10 / 0;
} catch (Exception e) {
    e.printStackTrace();
}

实际项目中通常使用日志框架:

log.error("计算失败", e);

16.3 finally 中不要写复杂逻辑

finally 最好只做资源释放工作。

不推荐:

finally {
    // 大量业务逻辑
}

推荐:

finally {
    // 关闭资源、释放连接
}

16.4 finally 中不要写 return

不推荐:

finally {
    return result;
}

因为它可能覆盖 trycatch 中的返回值,也可能吞掉异常。


16.5 不要过度使用异常控制业务流程

不推荐:

try {
    Integer.parseInt(str);
} catch (NumberFormatException e) {
    // 使用异常判断是否为数字
}

异常处理的成本较高,也会降低代码可读性。能用普通判断解决的问题,尽量不要依赖异常机制。


总结

try-catch-finally 是 Java 异常处理机制的核心。

关键字 作用
try 放置可能出现异常的代码
catch 捕获并处理异常
finally 通常用于释放资源,无论是否异常都会执行
throws 将异常抛给调用者处理
try-with-resources 自动关闭资源,推荐用于 IO、数据库连接等场景

开发中需注意:

  • 能明确捕获具体异常,就不要直接捕获 Exception
  • 不要空 catch
  • 不要在 finally 中写 return
  • 资源释放优先使用 try-with-resources
  • 异常处理应该服务于程序稳定性,而不是替代正常业务逻辑

掌握 try-catch-finally,不仅可以让程序更加健壮,也能帮助我们更好地定位和解决运行时问题。

更多推荐