【Java】Java 中的 try-catch-finally 详解
【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,因为 str 是 null,不能调用 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 中有 return,finally 也会执行。
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 不执行的情况:
- 调用了
System.exit() - JVM 崩溃
- 操作系统强制杀死进程
- 程序所在机器突然断电
- 守护线程中的 finally 还未执行,所有非守护线程已经结束
8 finally 中不要随便写 return
这是一个非常重要的坑。
如果 finally 中写了 return,它会覆盖 try 或 catch 中的返回值。
示例:
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());
}
}
}
只要资源类实现了 AutoCloseable 或 Closeable 接口,就可以使用 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;
}
因为它可能覆盖 try 或 catch 中的返回值,也可能吞掉异常。
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,不仅可以让程序更加健壮,也能帮助我们更好地定位和解决运行时问题。
更多推荐




所有评论(0)