Date类本质是UTC毫秒计数器,toString()伪装成本地时间;其可变性、0基月份、线程不安全及设计缺陷使其被java.time替代。

Java 的 Date 类不是“过时了”,而是从设计第一天起就和现代时间模型不兼容——它名义上叫 Date,实际是个带误导性 toString() 的毫秒计数器。
为什么 new Date() 打印出来像本地时间,却不是本地时间?
Date 内部只存一个 long 值:自 1970-01-01 00:00:00 UTC 起的毫秒数(即 Instant)。但它的 toString() 方法偷偷调用系统默认时区格式化输出,造成“它含时区”的错觉。
- 你写
new Date(),得到的是一个 UTC 时间点,不是“北京时间”或“纽约时间” - 跨时区服务中,若把
Date当作“无时区时间”传给前端或数据库,很可能在新加坡服务器上存了中国用户提交的“2026-01-19 10:00:00”,结果查出来变成 “2026-01-19 02:00:00”(因误按 UTC 解析) - 真正需要“本地日期+时间”语义时,该用
LocalDateTime;需要明确时区转换时,该用ZonedDateTime或Instant
月份从 0 开始、年份要减 1900:不是 bug,是历史包袱
这是最常踩的坑——date.setMonth(1) 不是设成 1 月,而是 2 月;date.setYear(126) 表示的是 2026 年(1900 + 126),但如果你手抖写了 setYear(2026),那恭喜,你创建了一个 3926 年的日期。
- 所有
getXXX()/setXXX()方法(如getMonth()、getDate()、getHours())都已标为@Deprecated,编译器会警告,运行时也可能出错 - 这种偏移不是 Java 特有,而是照搬 C 语言
struct tm,但 Java 早已不需要向后兼容这种底层细节 - 替代方案:用
LocalDateTime.of(2026, 1, 19, 18, 42),参数含义一目了然,且编译期就能校验范围
可变性 + 线程不安全 = 隐形炸弹
Date 是可变对象,任何持有引用的地方都能调用 setTime()、setHours() 直接改掉原始值。这在多线程、集合键、DTO 传递等场景下极易引发数据污染。
立即学习“Java免费学习笔记(深入)”;
- 把它放进
HashSet当 key,之后又调用setTime(),哈希码改变,对象再也找不回来 - Spring MVC 接收 JSON 时若用
Date字段,反序列化后可能被拦截器或业务逻辑意外修改,下游拿到的是被篡改的时间 -
java.time全家桶(LocalDateTime、Instant、ZonedDateTime)都是final+ 不可变,赋值即拷贝,天然线程安全
SimpleDateFormat 和 Calendar:补丁套补丁,越补越危险
想格式化?得配 SimpleDateFormat;想加一天?得转 Calendar;想解析字符串?还得靠它——而这两个类全是线程不安全的重灾区。
-
SimpleDateFormat内部维护Calendar实例和缓冲区,多线程共享静态实例时,常见输出 “2026-13-19” 或直接抛NumberFormatException -
Calendar的get()/set()同样沿用 0 基月份,且每次操作都要新建实例或重置状态,性能差、易遗漏 - 正确做法:
DateTimeFormatter.ISO_LOCAL_DATE_TIME是static final、线程安全;日期计算用localDateTime.plusDays(1),链式调用,不可变返回
迁移不是简单替换字段类型,关键是厘清语义:数据库里是 DATETIME(无时区)?那就换 LocalDateTime;是 TIMESTAMP WITH TIME ZONE?必须用 OffsetDateTime 或 ZonedDateTime;对外提供时间戳接口?统一走 Instant.now().toEpochMilli()。混淆这三者,比继续用 Date 更危险。










