Date类本质是UTC毫秒计数器,toString()伪装成本地时间;其可变性、0基月份、线程不安全及设计缺陷使其被java.time替代。
Java 的 Date 类不是“过时了”,而是从设计第一天起就和现代时间模型不兼容——它名义上叫 Date,实际是个带误导性 toString() 的毫秒计数器。
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
这是最常踩的坑——date.setMonth(1) 不是设成 1 月,而是 2 月;date.setYear(126) 表示的是 2026 年(1900 + 126),但如果你手抖写了 setYear(2026),那恭喜,你创建了一个 3926 年的日期。
getXXX()/setXXX() 方法(如 getMonth()、getDate()、getHours())都已标为 @Deprecated,编译器会警告,运行时也可能出错struct tm,但 Java 早已不需要向后兼容这种底层细节LocalDateTime.of(2026, 1, 19, 18, 42),参数含义一目了然,且编译期就能校验范围Date 是可变对象,任何持有引用的地方都能调用 setTime()、setHours() 直接改掉原始值。这在多线程、集合键、DTO 传递等场景下极易引发数据污染。
HashSet 当 key,之后又调用 setTime(),哈希码改变,对象再也找不回来Date 字段,反序列化后可能被拦截器或业务逻辑意外修改,下游拿到的是被篡改的时间java.time 全家桶(LocalDateTime、Instant、ZonedDateTime)都是 f
inal + 不可变,赋值即拷贝,天然线程安全想格式化?得配 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 更危险。