Skip to content

传统日期 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);

// 替代方案:Calendar

JDK 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 的问题总结:

  1. 非线程安全SimpleDateFormatCalendar 都线程不安全
  2. 设计反人类:月份从 0 开始、年份要减 1900
  3. 所有方法废弃Date 的大部分方法都废弃了
  4. 时区处理复杂:几乎没有正经的时区 API

结论:新项目直接用 JDK 8+ 的日期时间 API,旧项目尽量封装隔离,核心逻辑不要依赖旧的日期处理逻辑。

基于 VitePress 构建