异常处理
异常体系
Java异常体系结构?
答案:
Throwable
├── Error(错误)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception(异常)
├── RuntimeException(运行时异常,非受检异常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── ArithmeticException
│ └── IllegalArgumentException
└── 其他Exception(编译时异常,受检异常)
├── IOException
├── SQLException
├── ClassNotFoundException
└── InterruptedException分类:
- Error:严重错误,程序无法处理(如OOM)
- Exception:异常,程序可以处理
- 受检异常(Checked Exception):编译时必须处理
- 非受检异常(Unchecked Exception):运行时异常,可以不处理
受检异常和非受检异常的区别?
答案:
| 特性 | 受检异常 | 非受检异常 |
|---|---|---|
| 继承 | Exception(非RuntimeException) | RuntimeException |
| 编译检查 | 必须处理 | 可以不处理 |
| 常见异常 | IOException、SQLException | NullPointerException、ArrayIndexOutOfBoundsException |
| 使用场景 | 外部因素导致 | 程序逻辑错误 |
java
// 受检异常:必须处理
public void readFile() throws IOException {
FileReader fr = new FileReader("file.txt"); // 编译错误,必须处理
}
// 正确处理
public void readFile() {
try {
FileReader fr = new FileReader("file.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
// 非受检异常:可以不处理
public void divide(int a, int b) {
int result = a / b; // 可能抛出ArithmeticException,但不强制处理
}异常处理
异常处理的方式?
答案:
1. try-catch
java
try {
// 可能抛异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 处理异常
System.out.println("除数不能为0");
} catch (Exception e) {
// 捕获其他异常
e.printStackTrace();
} finally {
// 无论是否异常都执行
System.out.println("finally块");
}2. try-catch-finally执行顺序
java
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");
// return 3; // 不推荐在finally中return
}
}
// 输出:
// try
// finally
// 返回:1注意:
- finally一定会执行(除非JVM退出)
- finally中的return会覆盖try/catch中的return
- 不推荐在finally中return
3. try-with-resources(JDK7+)
java
// 自动关闭资源
try (FileReader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr)) {
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
// 等价于
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader("file.txt");
br = new BufferedReader(fr);
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}要求:资源类必须实现AutoCloseable接口
4. throws声明异常
java
public void readFile() throws IOException {
FileReader fr = new FileReader("file.txt");
}
// 调用者处理
public void test() {
try {
readFile();
} catch (IOException e) {
e.printStackTrace();
}
}5. throw抛出异常
java
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法");
}
this.age = age;
}多个catch的顺序?
答案: 必须从小到大(子类到父类),否则编译错误。
java
try {
// 代码
} catch (FileNotFoundException e) { // 子类
// 处理
} catch (IOException e) { // 父类
// 处理
} catch (Exception e) { // 最大的父类
// 处理
}
// 错误示例
try {
// 代码
} catch (Exception e) { // 父类在前
// 处理
} catch (IOException e) { // 编译错误:已被捕获
// 处理
}JDK7+多异常捕获:
java
try {
// 代码
} catch (IOException | SQLException e) {
// 同时捕获多个异常
e.printStackTrace();
}自定义异常
如何自定义异常?
答案:
java
// 自定义异常
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(String message) {
super(message);
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public int getCode() {
return code;
}
}
// 使用
public void transfer(Account from, Account to, double amount) {
if (from.getBalance() < amount) {
throw new BusinessException(1001, "余额不足");
}
// 转账逻辑
}
// 捕获
try {
transfer(account1, account2, 1000);
} catch (BusinessException e) {
System.out.println("错误码:" + e.getCode());
System.out.println("错误信息:" + e.getMessage());
}建议:
- 继承RuntimeException(非受检异常)
- 提供多个构造方法
- 添加错误码
异常最佳实践
异常处理的最佳实践?
答案:
1. 不要捕获Exception
java
// 不推荐
try {
// 代码
} catch (Exception e) { // 太宽泛
e.printStackTrace();
}
// 推荐
try {
// 代码
} catch (IOException e) { // 具体异常
// 处理
} catch (SQLException e) {
// 处理
}2. 不要吞掉异常
java
// 不推荐
try {
// 代码
} catch (Exception e) {
// 什么都不做,异常被吞掉
}
// 推荐
try {
// 代码
} catch (Exception e) {
logger.error("错误信息", e); // 记录日志
throw new BusinessException("业务异常", e); // 或抛出业务异常
}3. 不要用异常控制流程
java
// 不推荐
try {
while (true) {
array[i++] = getValue();
}
} catch (ArrayIndexOutOfBoundsException e) {
// 用异常结束循环
}
// 推荐
for (int i = 0; i < array.length; i++) {
array[i] = getValue();
}4. 异常信息要详细
java
// 不推荐
throw new Exception("错误");
// 推荐
throw new BusinessException(
String.format("用户[%s]余额[%.2f]不足,需要[%.2f]",
userId, balance, amount)
);5. 及时清理资源
java
// 推荐:使用try-with-resources
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
// 使用资源
} // 自动关闭6. 不要在finally中return
java
// 不推荐
public int test() {
try {
return 1;
} finally {
return 2; // 会覆盖try中的return
}
}7. 异常转换要保留原因
java
// 不推荐
try {
// 代码
} catch (SQLException e) {
throw new BusinessException("数据库错误"); // 丢失了原始异常
}
// 推荐
try {
// 代码
} catch (SQLException e) {
throw new BusinessException("数据库错误", e); // 保留原始异常
}常见异常
常见的运行时异常?
答案:
1. NullPointerException(空指针异常)
java
String str = null;
str.length(); // NPE2. ArrayIndexOutOfBoundsException(数组越界)
java
int[] arr = new int[5];
int value = arr[10]; // 越界3. ClassCastException(类型转换异常)
java
Object obj = "hello";
Integer num = (Integer) obj; // ClassCastException4. ArithmeticException(算术异常)
java
int result = 10 / 0; // 除以05. IllegalArgumentException(非法参数异常)
java
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
}6. NumberFormatException(数字格式异常)
java
int num = Integer.parseInt("abc"); // 格式错误7. ConcurrentModificationException(并发修改异常)
java
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String item : list) {
list.remove(item); // 并发修改异常
}
// 正确做法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}练习题
- finally块一定会执行吗?什么情况下不执行?
- try-catch-finally中都有return,最终返回哪个?
- 如何优雅地处理异常?
- 异常对性能有什么影响?