一、异常基础

  1. Java异常处理机制是怎样的?try、catch、finally、throw、throws的区别是什么?

    • 机制:通过try定义可能抛出异常的代码块,catch捕获并处理特定异常,finally确保资源释放(无论是否发生异常),throw主动抛出异常对象,throws在方法签名中声明可能抛出的异常。
    • 区别
      • try:包裹风险代码。
      • catch:捕获并处理指定异常类型。
      • finally:必执行块,用于清理资源。
      • throw:在方法内部抛出异常实例(e.g., throw new IOException())。
      • throws:声明方法可能抛出的异常类型(e.g., public void read() throws IOException)。
  2. 自定义异常如何创建和使用?

    • 创建:继承Exception(检查型异常)或RuntimeException(非检查型异常)。
      public class CustomException extends RuntimeException {
          public CustomException(String message) {
              super(message);
          }
      }
      
    • 使用:通过throw抛出,在方法中处理或声明。
  3. Error和Exception的区别是什么?如何处理?

    • 区别
      • Error:JVM严重问题(如OutOfMemoryError),程序无法恢复。
      • Exception:程序可处理的异常(如IOException)。
    • 处理
      • Error:通常不捕获,需修复代码或调整环境。
      • Exception:通过try-catch处理或向上传播。
  4. 检查型异常(Checked)和非检查型异常(Unchecked)的区别?

    • 检查型异常
      • 继承自Exception(非RuntimeException)。
      • 必须被捕获或在方法签名中声明(e.g., IOException)。
    • 非检查型异常
      • 继承自RuntimeExceptionError
      • 无需强制处理(e.g., NullPointerException, ArrayIndexOutOfBoundsException)。
  5. Java异常处理的优缺点?

    • 优点:分离正常逻辑与错误处理;类型安全;集中化处理。
    • 缺点:代码冗余;性能开销(构造栈跟踪);过度使用破坏代码可读性。
  6. 注意事项

    • 避免空catch块(至少记录日志)。
    • 优先捕获具体异常,再捕获通用异常。
    • finally中避免return或抛出异常,会覆盖try/catch的行为。
    • 使用try-with-resources自动管理资源(实现AutoCloseable接口)。
  7. 常见陷阱

    • 捕获Throwable/Exception后忽略(吞没异常)。
    • 在循环内部捕获异常导致性能问题。
    • 异常类型不匹配导致未被捕获。
  8. 性能影响

    • 创建成本:构造异常对象(尤其是填充栈跟踪)较慢。
    • 最佳实践:异常仅用于错误场景,避免用异常控制流程(如替代if判断)。
  9. 调试方法

    • 打印完整堆栈:e.printStackTrace()(仅限调试)。
    • 使用IDE的调试器捕获异常断点。
    • 日志记录异常链(logger.error("Error", e))。
  10. 日志记录方法

    • 使用日志框架(如SLF4J+Logback)记录异常:
      try {
          // ...
      } catch (Exception e) {
          logger.error("Operation failed", e); // 记录完整堆栈
      }
      

二、异常处理

  1. 捕获所有类型的异常

    try {
        // ...
    } catch (Throwable t) { // 捕获Error和Exception
        logger.error("Caught throwable", t);
    }
    

    注意:谨慎使用,通常仅在最外层捕获Throwable(如线程入口)。

  2. 抛出异常

    if (file == null) {
        throw new IllegalArgumentException("File cannot be null");
    }
    
  3. 声明异常

    public void readFile() throws IOException {
        // ...
    }
    
  4. 处理多个异常

    try {
        // ...
    } catch (IOException e) {
        // 处理IO异常
    } catch (SQLException e) {
        // 处理SQL异常
    } catch (Exception e) {
        // 兜底处理
    }
    

    Java 7+:多重捕获(Multi-catch)

    catch (IOException | SQLException e) {
        // 合并处理
    }
    
  5. 嵌套异常处理

    try {
        try {
            // 内层代码
        } catch (SpecificException e) {
            // 内层处理
            throw new WrapperException("Context", e); // 包装异常
        }
    } catch (WrapperException e) {
        // 外层处理
    }
    
  6. 异常链(Chained Exceptions)

    • 通过构造器传递原始异常:
      throw new ServiceException("Failed to call API", originalException);
      
    • 获取原始异常:e.getCause()
  7. 资源释放

    • 传统方式:在finally中关闭资源。
    • 现代方式:使用try-with-resources(Java 7+):
      try (FileInputStream fis = new FileInputStream("file.txt")) {
          // 使用资源
      } // 自动调用fis.close()
      
  8. 线程中的异常

    • 实现UncaughtExceptionHandler
      Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
          logger.error("Uncaught exception in thread: " + thread.getName(), throwable);
      });
      
  9. 异步操作中的异常

    • Future:通过Future.get()抛出ExecutionException(用e.getCause()获取原始异常)。
    • CompletableFuture:用exceptionally()handle()处理异常。
    • 线程池:重写afterExecute()方法(ThreadPoolExecutor)。
  10. 远程调用中的异常

    • 序列化异常(实现Serializable)。
    • 定义通用错误码和异常包装类(如RpcException)。
    • 服务端记录详细日志,客户端接收简化的错误信息。

三、异常设计

  1. 设计自定义异常

    • 继承合适的父类(业务异常选RuntimeException)。
    • 添加构造器(支持异常链)。
    • 包含业务相关字段(如错误码):
      public class PaymentFailedException extends RuntimeException {
          private String errorCode;
          public PaymentFailedException(String code, String message) {
              super(message);
              this.errorCode = code;
          }
      }
      
  2. 设计异常层次结构

    graph TD BaseBizException -->|extends| InvalidInputException BaseBizException -->|extends| PaymentException PaymentException -->|extends| InsufficientFundsException PaymentException -->|extends| CardDeclinedException
    • 原则:按业务领域分层,避免过度细化。
  3. 异常处理策略

    • 分层处理
      • Controller层:捕获所有异常,返回用户友好错误。
      • Service层:抛出业务异常,避免处理无关逻辑。
      • DAO层:抛出数据访问异常(如DataAccessException)。
    • 统一异常处理器(Spring的@ControllerAdvice)。
  4. 异常日志记录

    • 记录关键信息:时间、线程、异常类型、堆栈、业务参数。
    • 不同级别:
      • ERROR:系统错误(如数据库连接失败)。
      • WARN:可恢复问题(如重试成功)。
    • 避免重复记录(如底层已记录,上层不再记录)。
  5. 异常监控系统

    • 集成APM工具(如Prometheus+Grafana, ELK, Sentry)。
    • 监控指标:异常频率、类型分布、影响范围。
    • 设置报警阈值(如1小时内同异常出现50次)。
  6. 异常恢复机制

    • 重试:对瞬态错误(网络超时)有效。
    • 事务回滚:数据库操作失败时回滚。
    • 状态检查:恢复前验证系统状态(如检查文件是否存在)。
  7. 异常重试机制

    int maxRetries = 3;
    for (int i = 0; i <= maxRetries; i++) {
        try {
            callExternalService();
            break;
        } catch (RetryableException e) {
            if (i == maxRetries) throw e;
            Thread.sleep(1000 * i); // 指数退避
        }
    }
    

    工具:Spring Retry, Resilience4j.

  8. 异常降级机制

    • 主逻辑失败时返回备用结果:
      public Data getData() {
          try {
              return fetchFromRemote();
          } catch (Exception e) {
              return loadFromCache(); // 降级到缓存
          }
      }
      
  9. 熔断机制

    • 原理:当错误率超过阈值,熔断器打开,后续请求快速失败。
    • 工具:Hystrix, Resilience4j, Sentinel。
    • 状态机:Closed → Open → Half-Open → Closed。
  10. 异常隔离机制

    • 线程池隔离:不同服务使用独立线程池,避免雪崩。
    • 信号量隔离:限制并发调用数(如Semaphore)。
    • 容器隔离:微服务部署在不同容器/节点。

总结关键点

  • 基础:理解异常分类(Error/Exception, Checked/Unchecked)和关键字用法。
  • 实践:优先用try-with-resources管理资源,避免吞没异常,合理使用日志。
  • 设计:自定义异常要有业务意义,结合重试/降级/熔断构建鲁棒系统。
  • 性能:异常不应频繁用于控制流,栈跟踪生成有开销。