传统日期 API 的缺陷
你一定被 Java 的传统日期 API 坑过:Date 各种方法废弃、年份从 1900 开始、月份从 0 开始、非线程安全...
JDK 8 之前,Java 的日期处理一直是槽点满满。这篇讲清楚旧 API 的问题,也是理解新 API 价值的铺垫。
Date 类的设计缺陷
缺陷一:年份从 1900 开始
java
Date date = new Date(2026 - 1900, 2, 22);
System.out.println(date.getYear() + 1900); // 为什么要 +1900 ?年份是相对值而不是绝对值,用起来极其别扭。
缺陷二:月份从 0 开始
java
Date date = new Date(2026 - 1900, 2, 22); // 2 = 3月?坑爹啊!月份用 0~11 表示,3 月得传 2。
缺陷三:所有方法都废弃了
java
Date date = new Date();
// 这些方法都 @Deprecated 了
date.getYear();
date.getMonth();
date.getHours();
date.setDate(1);
// 替代方案:CalendarJDK 1.1 就开始废弃,但为了兼容一直保留到今天。
缺陷四:非线程安全
SimpleDateFormat 的老问题:
java
public class DateFormatDemo {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) throws Exception {
// 多线程环境下
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
try {
sdf.parse("2026-03-22"); // 线程不安全!
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}两个线程同时调用可能产生错误结果或异常。
Calendar 的设计缺陷
Calendar 比 Date 好一点,但也好不到哪去:
缺陷一:月份依然从 0 开始
java
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 2); // 依然是 3 月,不是 2 月和 Date 一样的坑,一脉相承。
缺陷二:设置字段需要组合
java
Calendar cal = Calendar.getInstance();
// 需要精确指定要设置的字段
cal.set(Calendar.YEAR, 2026);
cal.set(Calendar.MONTH, Calendar.MARCH);
cal.set(Calendar.DAY_OF_MONTH, 22);
// 一次性设置?不存在的缺陷三:非线程安全
Calendar 也不是线程安全的:
java
public class CalendarThreadUnsafe implements Runnable {
private static Calendar cal = Calendar.getInstance(); // 共享状态
@Override
public void run() {
cal.set(Calendar.DAY_OF_MONTH, 22);
System.out.println(cal.getTime()); // 并发时结果不可预测
}
}缺陷四:月份计算反人类
java
// 某月的最后一天怎么算?
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2026);
cal.set(Calendar.MONTH, Calendar.FEBRUARY); // 2 = 3月?
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.add(Calendar.DAY_OF_MONTH, -1); // 减一天才是月末计算一个月的最后一天要绕这么大一圈。
新旧对比
| 问题 | 旧 API | 新 API |
|---|---|---|
| 线程安全 | SimpleDateFormat 不安全 | DateTimeFormatter 线程安全 |
| 月份起点 | 0 = 1月 | 3 = 3月 |
| 时间增减 | add() 方法 | plusDays() 方法 |
| 月末计算 | add(Day, -1) | with(TemporalAdjusters.lastDayOfMonth()) |
| API 清晰度 | Date/Calendar 混乱 | LocalDate/LocalTime/LocalDateTime 分工明确 |
旧 API 的使用场景
新 API 虽好,但旧项目里还在用。典型场景:
数据库日期字段
java
// JDBC 仍然返回 java.sql.Date
java.sql.Date sqlDate = resultSet.getDate("create_date");
Date date = new Date(sqlDate.getTime()); // 转 util Date遗留系统对接
有些老系统接口返回的是 Date 格式,需要兼容:
java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date legacyDate = sdf.parse(legacyDateStr);总结
旧 API 的问题总结:
- 非线程安全:
SimpleDateFormat和Calendar都线程不安全 - 设计反人类:月份从 0 开始、年份要减 1900
- 所有方法废弃:
Date的大部分方法都废弃了 - 时区处理复杂:几乎没有正经的时区 API
结论:新项目直接用 JDK 8+ 的日期时间 API,旧项目尽量封装隔离,核心逻辑不要依赖旧的日期处理逻辑。
