Java开发中最易忽略的是运行时隐性异常:空指针(链式调用、自动拆箱等)、资源泄漏(未正确关闭IO/连接)、并发幽灵异常(ConcurrentModificationException等)及日期时区错误(SimpleDateFormat非线程安全、LocalDateTime误用)。

Java开发中,最容易被忽略的不是编译报错,而是那些“看似无害”却在运行时悄悄崩溃、数据错乱或资源泄露的隐性异常——它们不打断编译,不显式抛出,甚至日志里都难觅踪影。
空指针异常(NullPointerException)的隐形温床
很多人以为加了if (obj != null)就安全了,其实隐患常藏在链式调用、自动拆箱、集合取值、Optional误用等场景里。比如user.getAddress().getCity().toUpperCase(),中间任一环节为null都会炸;又如Integer id = map.get("id"); int value = id.intValue();,map没这个key时返回null,拆箱即NPE。
- 用
Objects.requireNonNull()在关键入参处主动拦截 - 链式调用改用
Optional.ofNullable(obj).map(...).orElse(...) - Map取值优先用
getOrDefault(key, defaultValue),避免null返回 - 启用IDEA的“Nullability annotations”(@Nullable/@NonNull)并开启编译期检查
资源未关闭导致的泄漏型异常
IO流、数据库连接、HTTP客户端、线程池等资源,若仅靠finally手动close,极易因异常跳过关闭逻辑;而try-with-resources虽好,但只适用于实现了AutoCloseable的类——很多自定义资源或老框架API并不支持。
- 所有实现
AutoCloseable的资源,必须用try-with-resources,禁用裸try-finally - 自定义资源类务必正确实现
close(),并在Javadoc中标明是否幂等 - 使用Apache Commons IO或Guava的工具类(如
IOUtils.closeQuietly())处理非标准资源 - 在单元测试中用
System.gc()+Thread.sleep()+ 日志观察资源释放情况(辅助验证)
并发场景下的“幽灵异常”
ConcurrentModificationException、IllegalMonitorStateException、死锁、ABA问题……这些不总抛异常,但一旦触发,往往伴随偶发、难复现、日志缺失。比如遍历ArrayList同时被另一线程修改;synchronized锁对象被意外变更;或用==比较volatile引用导致可见性误判。
立即学习“Java免费学习笔记(深入)”;
- 遍历集合优先用
CopyOnWriteArrayList或ConcurrentHashMap,而非同步包装类 - synchronized块锁对象必须是private final且生命周期稳定(忌用String、Integer等可变/共享对象)
- 多线程共享状态,优先用
AtomicInteger、StampedLock或ReentrantLock替代synchronized - 用JDK自带
jstack或Arthas实时dump线程栈,排查阻塞和锁竞争
日期与时区引发的静默错误
SimpleDateFormat非线程安全、Date毫秒值被误当秒处理、LocalDateTime无时区信息却参与跨系统时间计算、Calendar月份从0开始……这些不会抛异常,但结果偏差几小时甚至几天,上线后才发现订单超时、报表对不上。
- 全局禁用
SimpleDateFormat,统一用DateTimeFormatter(不可变、线程安全) - 所有时间存储用
Instant或带时区的ZonedDateTime,避免LocalDateTime单独落库 - 前后端时间交互强制约定ISO 8601格式(如
"2024-05-20T13:45:00Z"),服务端解析时明确指定ZoneId - 单元测试覆盖夏令时切换、跨年、跨月等边界时间点
基本上就这些——不是冷门知识,而是每天都在写的代码里反复踩坑的“熟面孔”。不复杂,但容易忽略。










