《Java 100 天进阶之路》第35篇:Java异常处理最佳实践

第35篇:Java异常处理最佳实践

📌 系列导航《Java 100 天进阶之路》完整目录 |

⬅️ 上一篇:第34篇:Java序列化与反序列化详解 |

➡️ 下一篇:第36篇:选择最适合自己的NIO,一探流技术


一、核心知识点

  • 异常处理的基本原则:只捕获能处理的异常,不要捕获后什么都不做
  • 检查型异常与运行时异常的使用场景
  • try-catch-finally 的正确用法
  • try-with-resources(自动关闭资源)
  • 自定义异常的规范
  • 异常链与异常包装
  • 日志记录的最佳实践

二、通俗讲解(1分钟开心学)

1. 异常处理的核心思想

  • 只捕获你能处理的异常。
  • 捕获后要做出有意义的响应(记录日志、返回默认值、重试、包装后重新抛出)。

2. 检查型异常 vs 运行时异常

  • 检查型异常 (如 IOException):调用者必须处理或声明抛出,代表可恢复的外部错误(文件不存在、网络中断)。
  • 运行时异常 (如 NullPointerException):不强制处理,代表编程错误(参数校验不足、数组越界),应当通过改进代码避免。

3. try-with-resources

自动关闭实现了 AutoCloseable 接口的资源(文件、网络连接、数据库连接),避免 finally 中手动关闭的繁琐。

4. 异常链

当你在一个异常中包装另一个异常时,可以保留原始异常信息,便于排查。

5. 日志记录

捕获异常后务必记录日志(使用 log.error("消息", e)),不要只打印 e.printStackTrace()(在正式环境可能不可见)。

6. 自定义异常

  • 继承 Exception(检查型)或 RuntimeException(运行时)。
  • 提供多个构造方法(无参、带消息、带消息+原异常)。

生活类比

异常处理就像开车遇到故障。能自己修的小毛病(运行时异常)就用工具修好(改进代码)。大问题(检查型异常)需要呼叫救援(try-catch),然后记录事故(日志)。

三、实操代码案例 + 场景说明

场景:读取配置文件,处理文件不存在的异常,并确保流被正确关闭。

java 复制代码
import java.io.*;
import java.util.Properties;
import java.util.logging.*;

public class ExceptionBestPractice {
    private static final Logger logger = Logger.getLogger("ExceptionDemo");
    
    // 1. 正确使用 try-with-resources 自动关闭
    public static String readFirstLine(String path) {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        } catch (IOException e) {
            // 记录日志,不要吞掉异常
            logger.log(Level.SEVERE, "读取文件失败: " + path, e);
            return null;  // 或抛出运行时异常
        }
    }
    
    // 2. 异常包装与链
    public static Properties loadConfig(String path) throws ConfigException {
        try (InputStream in = new FileInputStream(path)) {
            Properties props = new Properties();
            props.load(in);
            return props;
        } catch (IOException e) {
            // 包装为自定义异常,保留原始原因
            throw new ConfigException("加载配置失败: " + path, e);
        }
    }
    
    // 3. 运行时异常用于参数校验
    public static void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄必须在0~150之间,实际: " + age);
        }
        // 正常处理
    }
    
    // 4. 不要空 catch
    public static void badPractice() {
        try {
            int a = 10 / 0;
        } catch (ArithmeticException e) {
            // 什么都不做,问题被隐藏了!这是最坏的实践
        }
    }
    
    public static void main(String[] args) {
        String line = readFirstLine("nonexist.txt");
        System.out.println("读取结果: " + line);
        
        try {
            Properties props = loadConfig("config.properties");
        } catch (ConfigException e) {
            e.printStackTrace();  // 正式环境用 logger
        }
        
        try {
            setAge(200);
        } catch (IllegalArgumentException e) {
            System.err.println(e.getMessage());
        }
    }
}

// 自定义检查型异常
class ConfigException extends Exception {
    public ConfigException(String message, Throwable cause) {
        super(message, cause);
    }
    public ConfigException(String message) {
        super(message);
    }
}

四、避坑要点

错误/误区 后果 正确做法
捕获异常后什么都不做(空 catch) 掩盖问题,难以排查 至少记录日志
finally 中关闭资源但不处理异常 如果关闭失败,异常被吞没 使用 try-with-resources
抛出检查型异常但调用者无法处理 传递上层,导致处处 throws 考虑包装为运行时异常
在循环中 catch 异常 性能差,且可能陷入无限循环 循环外处理,或使用重试策略
日志只打印 e.getMessage() 丢失栈轨迹 使用 log.error("msg", e) 打印完整堆栈

五、面试高频考点

Q1:什么时候应该捕获检查型异常,什么时候应该向上抛出?

如果当前层能恢复或做出合理响应(如重试、使用默认值),则捕获;否则向上抛出,让上层处理。对于框架代码,常包装为运行时异常抛出。

Q2:try-with-resources 的原理是什么?

实现了 AutoCloseable 的资源可以在 try 括号中声明,编译后生成 finally 块调用 close(),并处理异常抑制(addSuppressed)。

Q3:如何正确记录异常日志?

使用日志框架(如 java.util.logging、Log4j、SLF4J),记录完整堆栈:logger.error("操作失败", e); 不要只记录 e.getMessage()

六、练习题

  1. 代码改错 :指出下面代码的问题并改正。

    java 复制代码
    try {
        FileInputStream fis = new FileInputStream("test.txt");
        fis.read();
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
  2. 设计 :写一个方法 divide(int a, int b),如果 b 为 0,抛出自定义运行时异常 DivideByZeroException

  3. 场景:你正在开发一个网络请求库,当网络超时时应如何处理异常?


📊 你的学习进度

  • 当前:第35篇 / 共44篇 · 第五阶段:工具类、异常最佳实践、序列化(第32~35篇)
  • ✅ 已完成:第1~34篇
  • 📖 正在学:第35篇
  • ⏳ 待学习:第36~44篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!


👉 下一篇文章预告

《第36篇:选择最适合自己的NIO,一探流技术》

内容简介:BIO/NIO/AIO区别、流体系(字节/字符/缓冲/转换)、NIO核心组件(Channel/Buffer/Selector)、零拷贝。

💡 学完这篇,你将掌握Java I/O的全貌,并能根据场景选择合适的I/O模型。

📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注 ,一起100天拿offer!

👉 点击关注我,更新后第一时间收到推送!

📌 除了Java,我也在深挖智能物流实战(出版社WMS、托盘调度、机器学习落地)。如果你对技术在不同领域的实战感兴趣,欢迎点击我的头像,看看专栏《出版社物流WMS智能调度实战》。技术相通,思路可鉴。

相关推荐
宇宙realman_9996 小时前
420B污染度等级查询代码
java·开发语言·算法
小白学大数据6 小时前
Playwright 爬虫:Python 爬取 JS 渲染的 JSP 网站
开发语言·javascript·爬虫·python·数据分析
用户8356290780516 小时前
使用 Python 创建 PowerPoint SmartArt 图形
后端·python
AI玫瑰助手6 小时前
Python函数:位置参数与关键字参数的使用
开发语言·python·信息可视化
如竟没有火炬6 小时前
乘法表中第K小的数——二分
开发语言·数据结构·python·算法·leetcode·职场和发展·动态规划
凯瑟琳.奥古斯特6 小时前
选择题专练数据库原理精选30题
开发语言·数据库·职场和发展·数据库开发
哈撒Ki6 小时前
前端性能优化汇总
前端·面试
用户2181697049306 小时前
golang HTTP (一) ListenAndServe NewServeMux http.HandleFunc
后端
苦逼的猿宝7 小时前
洗衣店订单管理系统(源码+论文)
java·毕业设计·springboot·计算机毕业设计