try-catch-finally

基本语法

try块里放”可能出事”的代码,catch块里写”出事了怎么办”

就像你过马路(try),万一被车撞了(catch),送医院处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testBasicTryCatch() {
System.out.println("=== 基本 try-catch ===");

try {
// 可能出错的代码放这里
int result = 10 / 0;
System.out.println("这行不会执行"); // try块中异常后面的代码不会执行!
} catch (ArithmeticException e) {
// 出错了怎么处理
System.out.println("捕获到异常:" + e.getMessage()); // / by zero
}
System.out.println("程序继续执行,不会崩"); // 正常执行
}

关键点:try块中一旦某行抛了异常,这行后面的代码不会执行,直接跳到catch块

多个catch

一个try可以配多个catch,就像医院有不同科室,什么病去什么科

规则:从具体到笼统排列,子类异常在前,父类异常在后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testMultiCatch() {
System.out.println("=== 多个 catch ===");

try {
String str = null;
System.out.println(str.length());
} catch (NullPointerException e) {
// 具体的异常放前面
System.out.println("空指针异常:" + e.getMessage());
} catch (RuntimeException e) {
// 父类异常放后面
System.out.println("运行时异常:" + e.getMessage());
} catch (Exception e) {
// 最大的兜底放最后
System.out.println("其他异常:" + e.getMessage());
}
}

为什么要从具体到笼统?

如果把 Exception 放第一个,后面的catch永远不会被执行到,编译器直接报错

就像你挂号先说”我浑身不舒服”,那导诊台没法给你分科室

Java 7+ 多异常合并写法

1
2
3
4
5
6
// 如果多种异常的处理方式一样,可以用 | 合并
try {
// ...
} catch (NullPointerException | ArithmeticException e) {
System.out.println("出错了:" + e.getMessage());
}

finally

无论是否发生异常,finally块都会执行

就像你不管考试考得好不好,回家都得吃饭

最常见的用途:关闭资源(文件流、数据库连接、网络连接)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testFinally() {
System.out.println("=== finally ===");

FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 读文件的操作...
System.out.println("读文件成功");
} catch (FileNotFoundException e) {
System.out.println("文件不存在:" + e.getMessage());
} finally {
// 不管上面成功还是失败,这里都会执行
System.out.println("finally执行了!");
if (fis != null) {
try {
fis.close(); // 关闭资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

你看上面的代码,光关一个文件流就写了一堆,又丑又长,所以Java 7引入了 try-catch-finally#try-with-resources

try-with-resources(Java 7+)

自动关闭资源,不用写finally了,代码简洁得多

条件:资源类必须实现 AutoCloseable 接口(大部分IO类都实现了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
public void testTryWithResources() {
System.out.println("=== try-with-resources ===");

// 旧写法:又臭又长
// BufferedReader br = null;
// try {
// br = new BufferedReader(new FileReader("test.txt"));
// ...
// } finally {
// if (br != null) br.close();
// }

// 新写法:把资源声明在 try() 的括号里,用完自动关
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line = br.readLine();
System.out.println("读到:" + line);
} catch (IOException e) {
System.out.println("读取失败:" + e.getMessage());
}
// 出了try块,br自动关闭,不用你管

// 多个资源?用分号隔开
try (
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")
) {
// 用完自动关闭,关闭顺序和声明顺序相反(后声明的先关)
} catch (IOException e) {
e.printStackTrace();
}
}

实战建议:只要是Java 7+的项目,永远用 try-with-resources 代替 try-finally 来关资源

finally中return的坑

这是经典面试题,也是实际开发中的大坑

finally中的return会覆盖try/catch中的return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testFinallyReturn() {
System.out.println("=== finally中的return ===");
System.out.println("结果:" + getValue()); // 输出:结果:30
}

public int getValue() {
try {
return 10; // 想返回10
} catch (Exception e) {
return 20; // 异常时返回20
} finally {
return 30; // ⚠️ 最终返回30!覆盖了try里的return
}
}

结论:永远不要在finally中写return! 这会让代码行为变得诡异,IDE也会给你警告

更阴险的例子:finally修改返回值

1
2
3
4
5
6
7
8
9
10
public int trickQuestion() {
int x = 1;
try {
return x; // 返回值在这里已经"定下来"了,是1
} finally {
x = 2; // 修改x不影响已经确定的返回值
}
// 最终返回 1,不是 2!
// 因为return语句会先把返回值保存起来,finally修改的是变量x,不是返回值
}

执行顺序详解

情况1:没有异常

try → finally → 方法返回

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testNoException() {
try {
System.out.println("1. try块执行");
} catch (Exception e) {
System.out.println("2. catch块执行"); // 不执行
} finally {
System.out.println("3. finally执行");
}
System.out.println("4. try-catch后面的代码");
// 输出:1 → 3 → 4
}

情况2:有异常,被catch接住

try(到异常那行为止)→ catch → finally → 继续往下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testWithException() {
try {
System.out.println("1. try块开始");
int x = 1 / 0; // 异常!
System.out.println("2. 这行不会执行");
} catch (ArithmeticException e) {
System.out.println("3. catch块执行");
} finally {
System.out.println("4. finally执行");
}
System.out.println("5. 继续执行");
// 输出:1 → 3 → 4 → 5
}

情况3:有异常,没被catch接住

try(到异常那行为止)→ finally → 异常继续往上抛 → 后面的代码不执行

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testUncaughtException() {
try {
System.out.println("1. try块开始");
int x = 1 / 0; // ArithmeticException
} catch (NullPointerException e) {
System.out.println("2. 类型不匹配,接不住"); // 不执行
} finally {
System.out.println("3. finally照样执行");
}
System.out.println("4. 这行不会执行"); // 异常没被处理,程序要崩了
// 输出:1 → 3 → 然后抛出 ArithmeticException
}

情况4:try中有return

try(执行到return,先算好返回值存起来)→ finally → 返回之前存好的值

场景 执行顺序
无异常 try → finally → 后续代码
有异常,catch匹配 try(部分) → catch → finally → 后续代码
有异常,catch不匹配 try(部分) → finally → 异常继续上抛
try中有return try → finally → 返回try中的值
finally中有return try → finally → 返回finally中的值(覆盖!)

常见坑

坑1:catch块里啥也不写(吞异常)

1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ 千万别这样!出了问题你一辈子找不到原因
try {
doSomething();
} catch (Exception e) {
// 空的... 异常被吃掉了
}

// ✅ 至少打个日志
try {
doSomething();
} catch (Exception e) {
log.error("操作失败", e); // 记录异常信息和堆栈
}

坑2:catch范围太大

别动不动就 catch (Exception e),应该catch你预期会出现的具体异常

太大的catch会掩盖你没预料到的bug

坑3:try块包太多代码

try块应该只包住可能出异常的那几行,不要把整个方法体都扔进去

范围越小,越容易定位问题

坑4:在finally里抛异常

finally里如果抛了新异常,会覆盖try/catch里的原始异常,原始异常就丢了

练习题

题1:说出下面代码的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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");
}
}
// 调用 test() 后:
// 打印:try → finally
// 返回值:1

题2:下面的代码返回值是多少?

1
2
3
4
5
6
7
8
9
10
public static int test2() {
int x = 0;
try {
x = 1;
return x; // 把1存为返回值
} finally {
x = 2; // 改了x,但返回值已经存好了
}
}
// 返回 1,不是 2

题3:用 try-with-resources 改写以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 原始代码
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("data.txt"));
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try { br.close(); } catch (IOException e) { e.printStackTrace(); }
}
}

// 改写后:
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}

相关笔记

异常体系 | throw与throws | 自定义异常


上一章 目录 下一章
异常体系 java基础 throw与throws