Java 8+ 的 java.time 包是 Date 和 Calendar 的现代替代方案,提供不可变、线程安全、语义清晰、支持链式调用的日期时间类,如 LocalDate、ZonedDateTime 等。

Java 中 Date 和 Calendar 都是早期日期处理类,但两者在易用性上存在明显缺陷,尤其在现代开发中已不推荐直接使用。
Date 的核心问题:可变性与设计混乱
Date 本意表示“时间戳”,却暴露了大量与日历计算相关的过时方法(如 getYear()、setMonth()),这些方法不仅索引错位(月份从 0 开始、年份需加 1900),而且线程不安全。更严重的是,Date 是可变对象——调用 setTime() 会直接修改原实例,容易引发隐蔽的并发或共享状态问题。
- 月份值为 0–11,违反直觉;
- 年份返回的是距 1900 年的偏移量(如 2024 返回 124);
- 所有 setter/getter 方法在 Java 8 后全部标记为
@Deprecated; - 没有内置时区支持,实际存储只是毫秒数,易误以为含日历语义。
Calendar 的主要痛点:冗长、状态耦合、API 不一致
Calendar 试图弥补 Date 的日历计算缺陷,但引入了更复杂的抽象:它是一个“日历系统+时间+时区+语言环境”的混合体,操作必须先 set() 再 get(),中间还依赖隐式 computeFields() 触发,逻辑链长且不易调试。
- 创建需通过
getInstance(),无法直接 new,且返回类型是抽象类; - 字段常量命名反直觉(如
Calendar.MONTH仍从 0 开始); - 增减日期需调用
add(field, amount),不能链式调用,也不支持局部字段更新; - 获取日期部分(如“本月第一天”)需手动设字段再
getTime(),步骤繁琐。
共性缺陷:缺乏不可变性、无流式 API、时区处理笨重
二者均未体现“日期/时间应是值对象”的设计思想。所有修改都是就地变更,无法安全共享或作为 Map 键使用。时区切换需重新构造 Calendar 实例或反复调用 setTimeZone(),而 Date.toString() 总按 JVM 默认时区格式化,掩盖真实毫秒值,造成调试困惑。
- 没有类似
plusDays(5)或withDayOfMonth(1)的清晰语义方法; - 格式化必须搭配
SimpleDateFormat(同样线程不安全),无法内建 ISO 格式支持; - 跨时区计算(如“东京上午9点对应纽约几点”)需手动转换毫秒并新建实例,极易出错。
替代方案:Java 8+ 的 java.time 包更直观可靠
真正解决上述问题的是 java.time 系列类:LocalDate(仅日期)、LocalDateTime(无时区时间)、ZonedDateTime(带时区完整时间)等均为不可变、线程安全、方法命名贴近自然语言,且支持链式调用和丰富的时间单位操作。
-
LocalDate.now().plusMonths(1).withDayOfMonth(1)直观表达业务意图; -
ZonedDateTime.of(...).withZoneSameInstant(ZoneId.of("America/New_York"))清晰分离时区转换逻辑; - 所有类默认 ISO 格式输出,解析/格式化统一通过
DateTimeFormatter,无需额外同步保护。
不复杂但容易忽略:旧 API 的“能用”不等于“该用”。迁移至 java.time 虽需少量学习成本,但换来的是可读性、健壮性和长期可维护性的显著提升。

















