引言
异常处理是Java编程的基础,但很多开发者对异常处理存在误区。本文将深入探讨Java异常机制、最佳实践和常见陷阱,帮助你写出更健壮的代码。
一、Java异常体系
1.1 异常层次结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Throwable ├── Error(错误) │ ├── OutOfMemoryError │ ├── StackOverflowError │ └── VirtualMachineError └── Exception(异常) ├── RuntimeException(运行时异常/非受检异常) │ ├── NullPointerException │ ├── IndexOutOfBoundsException │ ├── IllegalArgumentException │ └── ArithmeticException └── 受检异常(Checked Exception) ├── IOException ├── SQLException └── ClassNotFoundException
|
1.2 Error vs Exception
Error(错误)
1 2 3 4 5 6 7 8 9 10 11
|
List<byte[]> list = new ArrayList<>(); while (true) { list.add(new byte[1024 * 1024]); }
public void recursion() { recursion(); }
|
Exception(异常)
1 2 3 4 5 6
| try { int result = 10 / 0; } catch (ArithmeticException e) { System.out.println("除数不能为0"); }
|
1.3 受检异常 vs 非受检异常
受检异常(Checked Exception)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void readFile() throws IOException { FileReader reader = new FileReader("file.txt"); }
public void readFile() { try { FileReader reader = new FileReader("file.txt"); } catch (IOException e) { e.printStackTrace(); } }
|
非受检异常(Unchecked Exception)
1 2 3 4 5
| public void method() { String str = null; str.length(); }
|
二、异常处理基础
2.1 try-catch-finally
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public void method() { FileReader reader = null; try { reader = new FileReader("file.txt"); } catch (FileNotFoundException e) { System.err.println("文件不存在"); } catch (IOException e) { System.err.println("读取失败"); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
多重catch的顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| try { } catch (Exception e) { } catch (IOException e) { }
try { } catch (IOException e) { } catch (Exception e) { }
|
2.2 try-with-resources(JDK 7+)
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
| FileReader reader = null; try { reader = new FileReader("file.txt"); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
try (FileReader reader = new FileReader("file.txt")) { } catch (IOException e) { e.printStackTrace(); }
try (FileInputStream fis = new FileInputStream("in.txt"); FileOutputStream fos = new FileOutputStream("out.txt")) { } catch (IOException e) { e.printStackTrace(); }
|
自定义AutoCloseable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MyResource implements AutoCloseable { public void doSomething() { System.out.println("doing something"); } @Override public void close() { System.out.println("resource closed"); } }
try (MyResource resource = new MyResource()) { resource.doSomething(); }
|
2.3 throws声明异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void readFile(String path) throws IOException, FileNotFoundException { FileReader reader = new FileReader(path); }
public void caller() { try { readFile("file.txt"); } catch (IOException e) { e.printStackTrace(); } }
public void caller() throws IOException { readFile("file.txt"); }
|
2.4 throw抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("年龄必须在0-150之间"); } this.age = age; }
try { user.setAge(-1); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); }
|
三、自定义异常
3.1 创建自定义异常
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
| public class BusinessException extends RuntimeException { private int code; public BusinessException(int code, String message) { super(message); this.code = code; } public BusinessException(int code, String message, Throwable cause) { super(message, cause); this.code = code; } public int getCode() { return code; } }
public void transfer(Long from, Long to, BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw new BusinessException(4001, "转账金额必须大于0"); } BigDecimal balance = getBalance(from); if (balance.compareTo(amount) < 0) { throw new BusinessException(4002, "余额不足"); } }
|
3.2 异常体系设计
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 33 34 35 36 37 38
| public abstract class BaseException extends RuntimeException { private int code; private String message; public BaseException(int code, String message) { super(message); this.code = code; this.message = message; } }
public class UserNotFoundException extends BaseException { public UserNotFoundException(Long userId) { super(40401, "用户不存在:" + userId); } }
public class InsufficientBalanceException extends BaseException { public InsufficientBalanceException(BigDecimal balance, BigDecimal amount) { super(40001, String.format("余额不足:当前余额%.2f,需要%.2f", balance, amount)); } }
public class SystemException extends BaseException { public SystemException(String message) { super(50000, message); } public SystemException(String message, Throwable cause) { super(50000, message); initCause(cause); } }
|
四、异常处理最佳实践
4.1 不要捕获Throwable或Error
1 2 3 4 5 6 7 8 9 10 11 12 13
| try { } catch (Throwable t) { }
try { } catch (Exception e) { }
|
4.2 不要吞掉异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| try { } catch (Exception e) { }
try { } catch (Exception e) { logger.error("操作失败", e); }
try { } catch (Exception e) { logger.error("操作失败", e); throw new BusinessException("业务处理失败", e); }
|
4.3 不要使用异常进行流程控制
1 2 3 4 5 6 7 8 9 10 11 12 13
| try { while (true) { array[index++]; } } catch (ArrayIndexOutOfBoundsException e) { }
for (int i = 0; i < array.length; i++) { }
|
4.4 及早失败(Fail-Fast)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void process(String input) { if (input == null || input.isEmpty()) { throw new IllegalArgumentException("input不能为空"); } }
public void process(String input) { if (input == null) { throw new IllegalArgumentException("input不能为空"); } }
|
4.5 提供有意义的异常信息
1 2 3 4 5 6 7 8
| throw new Exception("出错了");
throw new BusinessException( 40401, String.format("用户不存在:userId=%d, username=%s", userId, username) );
|
4.6 记录异常栈信息
1 2 3 4 5 6 7 8 9
| catch (Exception e) { logger.error("错误:" + e.getMessage()); }
catch (Exception e) { logger.error("操作失败", e); }
|
4.7 不要在finally中抛异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| try { throw new RuntimeException("try异常"); } finally { throw new RuntimeException("finally异常"); }
try { throw new RuntimeException("try异常"); } finally { try { } catch (Exception e) { logger.error("资源清理失败", e); } }
|
4.8 返回空集合而非null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public List<User> getUsers() { return null; }
List<User> users = getUsers(); users.size();
public List<User> getUsers() { return Collections.emptyList(); }
List<User> users = getUsers(); users.size();
|
五、Spring中的异常处理
5.1 全局异常处理
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 33 34
| @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) { ErrorResponse error = new ErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception e) { logger.error("系统异常", e); ErrorResponse error = new ErrorResponse(50000, "系统错误"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationException( MethodArgumentNotValidException e) { String message = e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(", ")); ErrorResponse error = new ErrorResponse(40000, message); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); } }
@Data @AllArgsConstructor public class ErrorResponse { private int code; private String message; }
|
5.2 统一返回格式
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
| @Data @AllArgsConstructor public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> success(T data) { return new Result<>(200, "success", data); } public static <T> Result<T> error(int code, String message) { return new Result<>(code, message, null); } }
@RestController public class UserController { @GetMapping("/users/{id}") public Result<User> getUser(@PathVariable Long id) { User user = userService.getUser(id); if (user == null) { throw new BusinessException(40401, "用户不存在"); } return Result.success(user); } }
|
六、性能考虑
6.1 异常的性能开销
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class PerformanceTest { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { try { throw new Exception(); } catch (Exception e) { } } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start) + "ms"); } }
|
6.2 优化建议
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
|
for (int i = 0; i < 10000; i++) { try { if (condition) { throw new Exception(); } } catch (Exception e) { } }
for (int i = 0; i < 10000; i++) { if (condition) { } else { } }
private static final BusinessException INSUFFICIENT_BALANCE = new BusinessException(40001, "余额不足");
public void transfer() { if (balance < amount) { throw INSUFFICIENT_BALANCE; } }
|
七、常见陷阱
7.1 finally中的return
1 2 3 4 5 6 7 8
| public int method() { try { return 1; } finally { return 2; } }
|
7.2 异常链断裂
1 2 3 4 5 6 7 8 9 10 11 12 13
| try { } catch (SQLException e) { throw new BusinessException("数据库错误"); }
try { } catch (SQLException e) { throw new BusinessException("数据库错误", e); }
|
7.3 过度使用受检异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public User getUser(Long id) throws UserNotFoundException { }
try { User user = userService.getUser(id); } catch (UserNotFoundException e) { }
public User getUser(Long id) { if (user == null) { throw new BusinessException("用户不存在"); } return user; }
|
八、面试高频问题
8.1 Error和Exception的区别?
- Error:严重问题,程序无法处理(如OOM)
- Exception:可处理的异常,分为受检和非受检
8.2 finally一定会执行吗?
几乎一定执行,除非:
- System.exit()
- JVM崩溃
- 守护线程中断
8.3 try-catch-finally的执行顺序?
1 2
| try → catch → finally → 返回值 注意:finally中修改返回值无效(基本类型和不可变对象)
|
总结
异常处理最佳实践:
- 合理选择异常类型:受检 vs 非受检
- 提供详细信息:帮助定位问题
- 不要吞掉异常:至少记录日志
- 及早失败:尽早检查参数
- 使用try-with-resources:自动关闭资源
- 避免在finally中抛异常:会覆盖原异常
- 性能考虑:避免在热点代码中频繁抛异常
参考资料