Java基础语言进阶学习——4,Java异常体系和自定义异常
学习目标:深入理解Java异常体系和自定义异常
目录
学习目标:深入理解Java异常体系和自定义异常
一、异常体系的基本概念
1. 什么是异常?
2. Java异常体系结构
二、异常分类详解
1. 受检异常 (Checked Exception)
2. 非受检异常 (Unchecked Exception)
三、异常处理机制
1.核心概念区别
2.详细使用场景
a. try-catch-finally 使用场景
b. throws 使用场景
3. try-catch-finally 结构
4. throws 声明
5.总结
四、自定义异常
1. 为什么要自定义异常?
2. 自定义异常的实现方式
方式一:继承Exception(受检异常)
方式二:继承RuntimeException(非受检异常)
五、完整实战案例
用户服务系统异常处理
六、最佳实践和注意事项
1. 自定义异常设计原则
2. 异常处理最佳实践
七、总结
1.关键要点:
2.选择继承Exception还是RuntimeException?
注:此文章适合有一定Java基础的人群,有一些代码阅读量!
一、异常体系的基本概念
1. 什么是异常?
通俗理解:异常就是程序运行过程中出现的"意外情况",比如:
-
读取文件时文件不存在
-
网络连接突然断开
-
数组访问越界
-
数学运算除数为0
2. Java异常体系结构
Throwable (所有错误和异常的父类)
├── Error (系统级错误,程序无法处理)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── Exception (程序可以处理的异常)
├── RuntimeException (运行时异常,非受检异常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ └── ...
└── 其他Exception (受检异常)
├── IOException
├── SQLException
└── ...
二、异常分类详解
1. 受检异常 (Checked Exception)
-
核心特点:编译器强制处理(不处理无法编译)
- 产生场景:可预见的、调用者应处理的业务可恢复错误
-
常见类型:IOException、SQLException等
-
处理方式:try-catch或throws声明(必须捕获或声明抛出)
2. 非受检异常 (Unchecked Exception)
-
特点:RuntimeException及其子类,编译器不强制处理
- 产生场景:
- 代码缺陷(空指针、数组越界)
- 业务规则违例(参数非法、状态冲突)
- 不可控外部因素(用户输入错误、第三方库故障)
-
常见类型:NullPointerException、IllegalArgumentException等
- 处理原则:
1.基础框架层统一捕获(如Spring@ControllerAdvice)
2.业务层可不处理(需确保全局兜底)
三、异常处理机制
1.核心概念区别
| 特性 | try-catch-finally | throws |
|---|---|---|
| 作用 | 在当前方法内部处理异常 | 将异常抛给调用者处理 |
| 位置 | 方法体内 | 方法声明处 |
| 责任 | 自己处理异常 | 让别人处理异常 |
| 控制流 | 异常被捕获,程序可以继续执行 | 异常抛出,当前方法立即结束 |
2.详细使用场景
a. try-catch-finally 使用场景
适用情况:当你知道如何处理异常,并且希望程序能够继续执行时。
b. throws 使用场景
适用情况:当你不知道如何处理异常,或者异常应该由调用者决定如何处理时。
3. try-catch-finally 结构
public class ExceptionDemo {public static void main(String[] args) {try {// 可能抛出异常的代码int result = divide(10, 0);System.out.println("结果: " + result);} catch (ArithmeticException e) {// 捕获特定异常System.out.println("捕获到算术异常: " + e.getMessage());} catch (Exception e) {// 捕获其他异常System.out.println("捕获到其他异常: " + e.getMessage());} finally {// 无论是否发生异常都会执行System.out.println("finally块执行完成");}}public static int divide(int a, int b) {return a / b; // 可能抛出ArithmeticException}
}
4. throws 声明
public class ThrowsDemo {// 在方法声明中使用throws抛出异常public static void readFile(String filename) throws IOException {FileInputStream file = new FileInputStream(filename);// 文件操作...file.close();}public static void main(String[] args) {try {readFile("test.txt");} catch (IOException e) {System.out.println("文件读取失败: " + e.getMessage());}}
}
5.总结
记住这个简单的原则:
-
try-catch-finally:"我来处理这个麻烦"
-
throws:"这个麻烦交给你来处理"
在实际开发中,通常的做法是:
-
底层技术层(DAO):多用throws
-
业务逻辑层(Service):混合使用,转换异常类型
-
表现层(Controller):多用try-catch,进行最终处理
四、自定义异常
1. 为什么要自定义异常?
-
业务需求:Java内置异常无法满足特定业务场景
-
明确语义:让异常名称更符合业务逻辑
-
统一处理:便于对特定业务异常进行统一管理
2. 自定义异常的实现方式
方式一:继承Exception(受检异常)
/*** 自定义业务异常 - 受检异常* 用于表示用户相关的业务异常*/
public class UserNotFoundException extends Exception {private String userId;// 无参构造public UserNotFoundException() {super("用户不存在");}// 带消息的构造public UserNotFoundException(String message) {super(message);}// 带消息和用户ID的构造public UserNotFoundException(String message, String userId) {super(message);this.userId = userId;}// 带消息和原因的构造public UserNotFoundException(String message, Throwable cause) {super(message, cause);}public String getUserId() {return userId;}@Overridepublic String getMessage() {if (userId != null) {return super.getMessage() + " [用户ID: " + userId + "]";}return super.getMessage();}
}
方式二:继承RuntimeException(非受检异常)
/*** 自定义业务异常 - 非受检异常* 用于表示参数校验失败等运行时异常*/
public class InvalidParameterException extends RuntimeException {private String fieldName;private Object invalidValue;public InvalidParameterException(String fieldName, Object invalidValue) {super("参数 " + fieldName + " 的值 " + invalidValue + " 无效");this.fieldName = fieldName;this.invalidValue = invalidValue;}public InvalidParameterException(String message, String fieldName, Object invalidValue) {super(message);this.fieldName = fieldName;this.invalidValue = invalidValue;}// getter方法public String getFieldName() {return fieldName;}public Object getInvalidValue() {return invalidValue;}
}
五、完整实战案例
用户服务系统异常处理
import java.util.HashMap;
import java.util.Map;// 用户类
class User {private String id;private String name;private int age;public User(String id, String name, int age) {this.id = id;this.name = name;this.age = age;}// getter方法...public String getId() { return id; }public String getName() { return name; }public int getAge() { return age; }
}
// 用户服务类
class UserService {private Map<String, User> userDatabase = new HashMap<>();// 添加用户public void addUser(User user) throws InvalidUserException {// 参数校验if (user == null) {throw new InvalidUserException("用户对象不能为null");}if (user.getId() == null || user.getId().trim().isEmpty()) {throw new InvalidUserException("用户ID不能为空", "id", user.getId());}if (user.getAge() < 0 || user.getAge() > 150) {throw new InvalidUserException("年龄必须在0-150之间", "age", user.getAge());}// 检查用户是否已存在if (userDatabase.containsKey(user.getId())) {throw new UserAlreadyExistsException("用户已存在: " + user.getId(), user.getId());}userDatabase.put(user.getId(), user);System.out.println("用户添加成功: " + user.getName());}// 查找用户public User findUser(String userId) throws UserNotFoundException {User user = userDatabase.get(userId);if (user == null) {throw new UserNotFoundException("用户不存在", userId);}return user;}// 删除用户public void deleteUser(String userId) throws UserNotFoundException {if (!userDatabase.containsKey(userId)) {throw new UserNotFoundException("无法删除不存在的用户", userId);}userDatabase.remove(userId);System.out.println("用户删除成功: " + userId);}
}
// 自定义异常类
class InvalidUserException extends RuntimeException {private String fieldName;private Object invalidValue;public InvalidUserException(String message) {super(message);}public InvalidUserException(String message, String fieldName, Object invalidValue) {super(message);this.fieldName = fieldName;this.invalidValue = invalidValue;}public String getFieldName() { return fieldName; }public Object getInvalidValue() { return invalidValue; }
}class UserAlreadyExistsException extends RuntimeException {private String userId;public UserAlreadyExistsException(String message, String userId) {super(message);this.userId = userId;}public String getUserId() { return userId; }
}
// 测试类
public class CustomExceptionDemo {public static void main(String[] args) {UserService userService = new UserService();// 测试正常流程try {User user1 = new User("001", "张三", 25);userService.addUser(user1);User foundUser = userService.findUser("001");System.out.println("找到用户: " + foundUser.getName());} catch (InvalidUserException | UserNotFoundException e) {System.out.println("业务异常: " + e.getMessage());}// 测试异常情况System.out.println("\n=== 测试异常情况 ===");// 1. 测试无效用户try {User invalidUser = new User("", "李四", 25);userService.addUser(invalidUser);} catch (InvalidUserException e) {System.out.println("捕获到无效用户异常: " + e.getMessage());if (e.getFieldName() != null) {System.out.println("问题字段: " + e.getFieldName() + ", 无效值: " + e.getInvalidValue());}}// 2. 测试重复用户try {User user2 = new User("001", "王五", 30); // 与之前ID重复userService.addUser(user2);} catch (UserAlreadyExistsException e) {System.out.println("捕获到用户已存在异常: " + e.getMessage());}// 3. 测试查找不存在的用户try {userService.findUser("999");} catch (UserNotFoundException e) {System.out.println("捕获到用户不存在异常: " + e.getMessage());System.out.println("查找的用户ID: " + e.getUserId());}// 4. 测试年龄异常try {User ageUser = new User("003", "赵六", -5);userService.addUser(ageUser);} catch (InvalidUserException e) {System.out.println("捕获到年龄异常: " + e.getMessage());}}
}
六、最佳实践和注意事项
1. 自定义异常设计原则
-
命名规范:异常名以"Exception"结尾,清晰表达异常含义
-
提供多个构造方法:支持不同使用场景
-
包含有用信息:提供有助于问题定位的额外信息
-
保持不可变性:异常对象应该是不可变的
2. 异常处理最佳实践
public class ExceptionBestPractice {// 好的做法:提供详细的错误信息public void goodPractice(String input) {if (input == null || input.trim().isEmpty()) {throw new IllegalArgumentException("输入参数不能为空或空白字符串");}// 业务逻辑...}// 不好的做法:过于简单的错误信息public void badPractice(String input) {if (input == null) {throw new IllegalArgumentException("参数错误"); // 信息不明确}}// 好的做法:异常链保持public void processFile(String filename) {try {// 文件处理逻辑} catch (IOException e) {// 保留原始异常信息throw new BusinessException("文件处理失败: " + filename, e);}}// 不要忽略异常public void dontIgnoreException() {try {// 某些操作} catch (Exception e) {// 不好的做法:空的catch块// 好的做法:至少记录日志或采取恢复措施System.err.println("发生异常: " + e.getMessage());// 或者记录日志: logger.error("处理失败", e);}}
}
七、总结
1.关键要点:
-
异常分类:理解受检异常和非受检异常的区别
-
处理机制:掌握try-catch-finally和throws的使用
-
自定义异常:根据业务需求创建合适的异常类
-
最佳实践:提供清晰的错误信息,不要忽略异常
2.选择继承Exception还是RuntimeException?
-
继承
Exception(受检异常)当且仅当:- 调用者可具体处理恢复(如重试上传)
- 低频且严重的错误
反例: 登录密码错误(高频场景)不应设计为受检异常
-
继承
RuntimeException(非受检异常)的条件:- 调用者无法干预修复(如第三方服务崩溃)
- 业务高频触发的规则违例(如库存不足)
- 框架设计考量(避免污染Lambda表达式)
