Skip to content

异常处理

异常体系

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、SQLExceptionNullPointerException、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();  // NPE

2. ArrayIndexOutOfBoundsException(数组越界)

java
int[] arr = new int[5];
int value = arr[10];  // 越界

3. ClassCastException(类型转换异常)

java
Object obj = "hello";
Integer num = (Integer) obj;  // ClassCastException

4. ArithmeticException(算术异常)

java
int result = 10 / 0;  // 除以0

5. 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();
}

练习题

  1. finally块一定会执行吗?什么情况下不执行?
  2. try-catch-finally中都有return,最终返回哪个?
  3. 如何优雅地处理异常?
  4. 异常对性能有什么影响?

Released under the MIT License.