new Date() 踩坑主因有三:月份从0开始、年份以1900为基准、getDay()返回星期而非日期;Date可变且线程不安全;toString()按本地时区显示却无时区信息。

为什么 new Date() 一用就踩坑?—— 年份和月份的“0基陷阱”
你写 date.setMonth(1),本意是设为1月,结果变成2月;写 date.setYear(2023),实际存的是3923年(2023 + 1900)。这不是bug,是设计:月份从0开始(0 = January),年份以1900为基准。这种反直觉偏移没有编译检查,运行时才暴露,调试时只能靠print加猜。
-
getMonth()返回0~11,不是1~12 -
getYear()返回距1900年的偏移量,比如2026年返回126 -
getDate()是“日”(1~31),但getDay()却是“星期几”(0=Sunday),命名完全误导
为什么多线程里 Date 像个定时炸弹?—— 可变性与线程不安全
Date 是可变对象,所有 setXXX() 方法都直接改原对象。一旦被多个线程共享(比如作为静态工具类字段、缓存值、Map的key),谁都能悄悄调用 setTime() 或 setYear(),导致时间错乱、哈希码突变、集合失效。
- 用作
HashSet或HashMap的 key 后再修改,对象就“消失”了 -
SimpleDateFormat非线程安全,常被误配成static final,引发解析错乱(如把 "2025-01-01" 解成 "2025-02-01") - 框架(如Spring、Hibernate)内部必须做防御性拷贝,徒增开销
为什么 toString() 显示的和你以为的不一样?—— 时区语义分裂
Date 内部只存一个毫秒值(UTC纪元偏移),它本身**不含时区信息**。但 toString() 默认按 JVM 本地时区格式化输出,造成“Date带时区”的错觉。你看到 Mon Jan 19 13:59:00 CST 2026,只是展示方式,不代表它“属于”CST。
- 跨时区服务中,直接用
Date传参或比较,极易混入隐式时区转换 - 想转纽约时间?得套一层
Calendar或SimpleDateFormat,代码冗长且易忽略夏令时 - 没有类型区分:你无法从变量名或类型知道它是“瞬时点”还是“本地日期”,全靠注释或上下文猜
怎么替?别硬扛,用对 java.time 的类就行
替换不是全盘重写,而是按语义选对类型:Instant 替 Date(纯时间点),LocalDateTime 替“仅业务日期时间”,ZonedDateTime 替需显式时区的场景。注意:数据库字段、JSON序列化、MyBatis等需同步适配。
立即学习“Java免费学习笔记(深入)”;
- 当前时间:用
Instant.now()或LocalDateTime.now(),不是new Date() - 数据库映射:JPA 2.2+ 支持
@Column(columnDefinition = "TIMESTAMP")+LocalDateTime;老版本需自定义AttributeConverter - JSON 序列化:Jackson 默认支持
java.time,但需确认ObjectMapper已注册JavaTimeModule
最常被忽略的一点:旧代码里 Date.getTime() 返回 long,新代码里 Instant.toEpochMilli() 才等价;别直接拿 LocalDateTime.toInstant(),它缺时区,会抛异常。










