第18课:异常处理
本课目标:理解异常处理机制,掌握try-catch-finally、throw/throws的使用,能够创建和处理自定义异常。
一、概念讲解
1.1 什么是异常
异常是程序运行时发生的不正常事件,会中断程序的正常执行流程。Java的异常处理机制让程序能够优雅地处理错误,而不是直接崩溃。
1.2 异常体系
php
异常继承体系:
Throwable
/ \
Error Exception
/ \
OutOfMemoryError RuntimeException
StackOverflowError / \
NullPointerException
ArrayIndexOutOfBounds
ArithmeticException
...
IOException
SQLException
FileNotFoundException
...
Error:系统级错误,程序无法处理(如内存溢出)
RuntimeException:运行时异常,可处理可不处理
Exception(非Runtime):编译时异常,必须处理
1.3 Checked vs Unchecked异常
| 类型 | 特点 | 示例 |
|---|---|---|
| Checked Exception | 编译时检查,必须处理 | IOException, SQLException |
| Unchecked Exception | 运行时才出现,可以不处理 | NullPointerException, ArrayIndexOutOfBoundsException |
| Error | 系统错误,程序无法处理 | OutOfMemoryError, StackOverflowError |
二、语法格式
2.1 try-catch-finally
java
try {
// 可能抛出异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 处理特定异常
System.out.println("算术错误: " + e.getMessage());
} catch (Exception e) {
// 处理其他异常
System.out.println("其他错误: " + e.getMessage());
} finally {
// 无论如何都会执行(除非JVM退出)
System.out.println("清理工作");
}
2.2 throw和throws
java
// throw:在方法体内抛出异常
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
}
// throws:在方法签名上声明可能抛出的异常
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path);
// ...
}
// throws可以声明多个异常
public void process(String path) throws IOException, SQLException {
// ...
}
2.3 自定义异常
java
// 自定义受检异常
public class BusinessException extends Exception {
private int code;
public BusinessException(String message) {
super(message);
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
// 自定义运行时异常
public class InvalidDataException extends RuntimeException {
public InvalidDataException(String message) {
super(message);
}
}
2.4 Try-with-resources
java
// 自动关闭资源(JDK7+)
try (FileReader reader = new FileReader("file.txt");
BufferedReader br = new BufferedReader(reader)) {
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("读取失败: " + e.getMessage());
}
// reader和br会自动关闭,无需finally
三、代码案例
3.1 带异常处理的文件读取器
java
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SafeFileReader {
public static List<String> readLines(String filePath) {
List<String> lines = new ArrayList<>();
// 检查文件是否存在
File file = new File(filePath);
if (!file.exists()) {
System.out.println("文件不存在: " + filePath);
return lines;
}
// 使用try-with-resources自动关闭资源
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
lines.add(line);
lineNumber++;
}
System.out.println("成功读取 " + lines.size() + " 行");
} catch (FileNotFoundException e) {
System.out.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
System.out.println("读取文件失败: " + e.getMessage());
} catch (Exception e) {
System.out.println("未知错误: " + e.getMessage());
}
return lines;
}
public static boolean writeLines(String filePath, List<String> lines) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
System.out.println("成功写入 " + lines.size() + " 行");
return true;
} catch (IOException e) {
System.out.println("写入文件失败: " + e.getMessage());
return false;
}
}
public static void main(String[] args) {
System.out.println("=== 文件读取器 ===");
// 写入测试数据
List<String> data = new ArrayList<>();
data.add("Hello World");
data.add("Java 异常处理");
data.add("Try-with-resources");
writeLines("test.txt", data);
// 读取文件
List<String> lines = readLines("test.txt");
for (String line : lines) {
System.out.println(" " + line);
}
// 读取不存在的文件
System.out.println();
readLines("nonexistent.txt");
}
}
▶ 运行结果:
vbnet
=== 文件读取器 ===
成功写入 3 行
成功读取 3 行
Hello World
Java 异常处理
Try-with-resources
文件不存在: nonexistent.txt
3.2 带验证的计算器
java
public class Calculator {
// 自定义异常
public static class DivisionByZeroException extends Exception {
public DivisionByZeroException() {
super("除数不能为零");
}
}
public static class InvalidNumberException extends Exception {
private String input;
public InvalidNumberException(String input) {
super("无效的数字: " + input);
this.input = input;
}
public String getInput() {
return input;
}
}
public static double parseNumber(String input) throws InvalidNumberException {
if (input == null || input.trim().isEmpty()) {
throw new InvalidNumberException(input);
}
try {
return Double.parseDouble(input.trim());
} catch (NumberFormatException e) {
throw new InvalidNumberException(input);
}
}
public static double divide(double a, double b) throws DivisionByZeroException {
if (b == 0) {
throw new DivisionByZeroException();
}
return a / b;
}
public static double calculate(String a, String operator, String b) {
try {
double num1 = parseNumber(a);
double num2 = parseNumber(b);
switch (operator) {
case "+": return num1 + num2;
case "-": return num1 - num2;
case "*": return num1 * num2;
case "/": return divide(num1, num2);
default:
System.out.println("不支持的运算符: " + operator);
return 0;
}
} catch (InvalidNumberException e) {
System.out.println("输入错误: " + e.getMessage());
return 0;
} catch (DivisionByZeroException e) {
System.out.println("计算错误: " + e.getMessage());
return 0;
}
}
public static void main(String[] args) {
System.out.println("=== 计算器 ===");
// 正常计算
System.out.println("10 + 5 = " + calculate("10", "+", "5"));
System.out.println("10 - 5 = " + calculate("10", "-", "5"));
System.out.println("10 * 5 = " + calculate("10", "*", "5"));
System.out.println("10 / 5 = " + calculate("10", "/", "5"));
System.out.println();
// 异常情况
System.out.println("10 / 0 = " + calculate("10", "/", "0"));
System.out.println("abc + 5 = " + calculate("abc", "+", "5"));
System.out.println(" + 5 = " + calculate("", "+", "5"));
}
}
▶ 运行结果:
ini
=== 计算器 ===
10 + 5 = 15.0
10 - 5 = 5.0
10 * 5 = 50.0
10 / 5 = 2.0
10 / 0 = 0.0
输入错误: 无效的数字: abc
输入错误: 无效的数字:
3.3 多异常处理示例
java
import java.util.*;
public class MultiExceptionDemo {
public static void processArray(int[] array, int index) {
try {
System.out.println("元素值: " + array[index]);
int result = 100 / array[index];
System.out.println("100 / " + array[index] + " = " + result);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界: 索引 " + index + " 不存在");
} catch (ArithmeticException e) {
System.out.println("算术错误: " + e.getMessage());
} catch (Exception e) {
System.out.println("其他错误: " + e.getClass().getSimpleName());
}
}
// 多重catch(JDK7+)
public static void multiCatch(String input) {
try {
int number = Integer.parseInt(input);
int result = 100 / number;
System.out.println("结果: " + result);
} catch (NumberFormatException | ArithmeticException e) {
// 一个catch处理多种异常
System.out.println("错误: " + e.getMessage());
} catch (Exception e) {
System.out.println("其他错误: " + e.getMessage());
}
}
// finally与return
public static int finallyWithReturn() {
int value = 10;
try {
value = 20;
return value; // finally会在return之前执行
} finally {
value = 30; // 但这不会影响返回值
System.out.println("finally中value = " + value);
}
}
public static void main(String[] args) {
System.out.println("=== 多异常处理 ===");
// 数组越界
processArray(new int[]{1, 2, 3}, 5);
// 算术异常
processArray(new int[]{1, 0, 3}, 1);
// 多重catch
System.out.println();
multiCatch("abc");
multiCatch("0");
multiCatch("5");
// finally与return
System.out.println();
int result = finallyWithReturn();
System.out.println("返回值: " + result); // 输出20,不是30
}
}
▶ 运行结果:
makefile
=== 多异常处理 ===
数组越界: 索引 5 不存在
算术错误: / by zero
错误: For input string: "abc"
算术错误: / by zero
结果: 20
finally中value = 30
返回值: 20
四、常见错误
4.1 捕获Exception而不捕获具体异常
java
public class CatchAllError {
public static void main(String[] args) {
// 错误:捕获所有异常,无法针对性处理
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]);
} catch (Exception e) { // 不推荐:太宽泛
System.out.println("出错了");
}
// 正确:捕获具体异常
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界: " + e.getMessage());
}
// 注意:catch块的顺序很重要,子类必须在父类前面
try {
// ...
} catch (ArrayIndexOutOfBoundsException e) {
// 具体异常
} catch (RuntimeException e) {
// 父类异常
} catch (Exception e) {
// 最顶层异常
}
}
}
4.2 空的catch块
java
public class EmptyCatchError {
public static void main(String[] args) {
// 错误:空catch块吞掉异常,问题被隐藏
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
// 什么都没做!错误被静默忽略
}
// 正确:至少记录日志或打印信息
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("捕获异常: " + e.getMessage());
e.printStackTrace(); // 打印堆栈信息
}
}
}
4.3 finally中的return
java
public class FinallyReturnError {
public static int testReturn() {
try {
return 1; // 返回1
} finally {
return 2; // 覆盖了try中的return!返回2
}
}
public static void main(String[] args) {
// 这是一个危险的模式,应该避免
int result = testReturn();
System.out.println("返回值: " + result); // 输出2,不是1
// 如果finally中没有return,才会返回try中的值
}
}
▶ 运行结果:
makefile
返回值: 2
4.4 异常处理不当
java
import java.io.*;
public class ImproperExceptionHandling {
public static void main(String[] args) {
// 错误:在循环中捕获异常后继续执行
int[] array = {1, 2, 0, 4};
for (int i = 0; i < array.length; i++) {
try {
int result = 100 / array[i];
System.out.println("100 / " + array[i] + " = " + result);
} catch (ArithmeticException e) {
System.out.println("跳过0");
continue; // 继续下一个
}
}
// 正确:先验证再操作
System.out.println();
for (int i = 0; i < array.length; i++) {
if (array[i] == 0) {
System.out.println("跳过0");
continue;
}
int result = 100 / array[i];
System.out.println("100 / " + array[i] + " = " + result);
}
}
}
▶ 运行结果:
ini
100 / 1 = 100
100 / 2 = 50
跳过0
100 / 4 = 25
跳过0
100 / 1 = 100
100 / 2 = 50
100 / 4 = 25
五、课后练习
-
异常练习:编写一个方法,输入字符串数组,将每个字符串转换为整数,处理NumberFormatException。
-
自定义异常:创建一个AgeInvalidException,验证年龄必须在0-150之间。
-
文件处理:编写一个文件复制程序,使用try-with-resources处理IOException。
-
链式异常:创建一个自定义异常,支持传递原始异常(cause)。
-
finally分析:分析以下代码的输出,解释原因:
java
public static int test() {
try {
return 1;
} finally {
return 2;
}
}
六、本课小结
| 概念 | 要点 |
|---|---|
| Throwable | 所有异常和错误的父类 |
| Error | 系统错误,程序无法处理 |
| Exception | 程序异常,分为Checked和Unchecked |
| try-catch-finally | 捕获和处理异常 |
| throw | 在方法体内抛出异常 |
| throws | 在方法签名声明可能抛出的异常 |
| 自定义异常 | 继承Exception或RuntimeException |
| try-with-resources | 自动关闭资源 |
关键规则:
- 先捕获具体异常,再捕获通用异常
- 不要捕获Exception而不做任何处理
- finally中的return会覆盖try中的return
- 优先使用try-with-resources自动关闭资源
本课程持续更新中,欢迎关注!