Java早期日期处理的核心问题是“一个类干太多事,又干得不直观”:Date本意表示时间点却暴露年月日操作接口,Calendar补足短板却堆砌易错常量和可变状态,导致月份算错、时区偏移等错误频发。

Java早期日期处理的核心问题是“一个类干太多事,又干得不直观”。Date本意是表示时间点,却暴露了年月日等字段操作接口;Calendar本想补足Date的短板,结果自己又堆砌了大量易错常量和可变状态。这种设计导致开发者频繁掉坑,不是月份算错,就是时区偏移,根源就在变量职责不清、状态随意变更。
Date类:名字误导+字段反直觉
Date表面叫“日期”,实际是毫秒时间戳的包装器。它不存年月日,只存自1970-01-01 00:00:00 GMT起的long值。但它的老方法却强行暴露日期语义:
- getYear() 返回的是“年份减1900”,比如2026年返回126
- getMonth() 从0开始计数,1月是0,12月是11
- getDate() 名字像“获取日期”,实际是“获取日(day of month)”,容易和java.time.LocalDate混淆
- 所有这些方法都已标记@Deprecated,但代码里仍常见,只因历史项目没重构
Calendar类:桥接者反而更复杂
Calendar本应是Date的增强版,但它用抽象类+静态工厂模式引入新混乱:
- 必须用Calendar.getInstance()获取实例,背后默认绑定系统时区,隐式依赖环境
- 字段操作全靠整数常量:Calendar.MONTH、Calendar.DAY_OF_YEAR等,写错常量名或传错值很难被编译器捕获
- set()方法有多个重载,比如set(year, month, date)和set(field, value)混用时容易漏掉时分秒,导致时间被重置为00:00:00
- get()和set()操作都不是原子的——修改年份后没调用getTime(),后续get可能返回未同步的中间状态
变量混乱的典型表现
不是代码写错,而是模型本身矛盾:
- 可变性泛滥:Date和Calendar对象都能被随时修改,传参时不敢直接共享引用,怕被上游或下游意外改掉
- 时区藏得太深:Date.toString()会按本地时区格式化输出,但getTime()拿到的却是UTC毫秒值,同一对象打印和计算结果看起来不一致
- 日期/时间无法解耦:想只处理“2026-05-10”这个日期?不行,Date和Calendar都强制带时间部分,哪怕你只设了年月日,时分秒也默认是当前时刻
- 线程安全真空:没有内置同步机制,多线程共用同一个Calendar实例时,get/set交叉执行会导致字段错乱,比如年份是2026、月份却是上一次设置的2(即3月)
为什么迁移到java.time是必要选择
不是因为旧API不能用,而是它把本该由类型系统约束的规则,交给了开发者凭记忆遵守:
- LocalDate 只管日期,不含时间,构造即不可变
- LocalDateTime 明确区分“本地时间”,不涉及时区解释
- ZonedDateTime 把时区作为一等公民,偏移量、ID、夏令时切换全部显式建模
- 所有核心类都是final且不可变,传参、缓存、并发使用无副作用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











