1. 概述

本文将深入对比 Java 中两个历史悠久的日期类:java.util.Datejava.sql.Date

虽然它们名字相似,继承关系也看似简单,但在实际开发中极易“踩坑”。通过本文,你将清晰掌握:

  • 各自的设计目的和使用场景
  • 关键差异与潜在陷阱
  • 现代 Java 开发中应如何正确处理时间

✅ 核心结论先行:
除非维护老项目,否则应优先使用 Java 8 引入的 java.time 包(如 LocalDate, Instant 等),避免使用 java.util.Datejava.sql.Date


2. java.util.Date:过时的时间表示

java.util.Date 类用于表示一个精确到毫秒的时间点,其基准是 1970年1月1日 00:00:00 GMT(即 Unix 时间戳的起始点)。

初始化方式

最常见两种构造方式:

// 方式1:当前时间
Date date = new Date();
// 方式2:指定时间戳(毫秒)
long timestamp = 1532516399000L; // 对应 2018年7月25日 10:59:59 UTC
Date date = new Date(timestamp);

⚠️ 注意:Java 8 之前的其他构造方法(如带年月日参数的)均已废弃,不建议使用。

主要问题

尽管广泛存在,java.util.Date 存在多个设计缺陷,导致其在现代开发中应尽量避免:

  • 可变性(Mutable)
    它不是不可变对象,内部状态可被修改,容易引发并发问题或意外行为。

    date.setTime(0); // 修改为 1970-01-01 00:00:00 GMT
    

    不可变性是现代时间类(如 LocalDateTime)的核心设计原则之一。

  • 时区处理模糊
    虽然文档声称表示 UTC 时间,但实际行为受 JVM 时区设置和操作系统影响,容易导致跨环境不一致。

  • 忽略闰秒
    大多数系统按 86400 秒/天计算,未考虑闰秒,严格来说不符合 UTC 定义。

替代方案

自 Java 8 起新增的 java.time 包(JSR-310)已成为标准实践:

  • ✅ 推荐使用:Instant, ZonedDateTime, LocalDateTime
  • ✅ 第三方替代(Java 8 之前):Joda-Time

📚 扩展阅读:Immutable Objects in Java


3. java.sql.Date:专为数据库设计的“伪日期”

java.sql.Date 继承自 java.util.Date,但用途非常明确:映射 SQL 标准中的 DATE 类型

核心特性

  • ✅ 只保留 年-月-日 信息,时间部分(时分秒)始终归零
  • ✅ 内部仍以毫秒存储(自 1970-01-01 00:00:00 GMT 起)
  • ✅ 构造时会自动“规范化”时间部分为 0
// 示例:将包含时间的 util.Date 转为 sql.Date
java.util.Date utilDate = new java.util.Date();
java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
// sqlDate 的时间部分已被设为 00:00:00

使用场景

仅应在以下情况使用:

  • ✅ JDBC 操作中与数据库 DATE 字段交互
  • ✅ MyBatis、JPA 等 ORM 框架映射 DATE 类型字段

注意事项

  • ⚠️ 时区转换依赖 JDBC 驱动实现
    java.sql.Date 本身不包含时区信息,JVM 与数据库之间的时区转换由 JDBC 驱动处理,不同厂商实现可能不同,容易引发数据偏差。

  • ✅ 配套类说明
    为完整支持 SQL 时间类型,java.sql 包还提供了:

    • Time:对应 SQL TIME(仅时间)
    • Timestamp:对应 SQL TIMESTAMP(含纳秒精度)

    其中 Timestamp 虽继承自 java.util.Date,但扩展支持了纳秒级精度。


4. 总结与最佳实践

特性 java.util.Date java.sql.Date
精度 毫秒 天(时间归零)
可变性 ✅ 可变 ✅ 可变
时区支持 ❌ 模糊 ❌ 依赖驱动
使用场景 ❌ 已废弃 ✅ 仅用于 JDBC
推荐程度 ❌ 不推荐 ⚠️ 仅限数据库交互

✅ 正确姿势

  1. 新项目一律使用 java.time

    • 日期:LocalDate
    • 时间戳:Instant
    • 带时区:ZonedDateTime
  2. 与数据库交互时

    • JPA/Hibernate:直接使用 LocalDate 映射 DATE 字段
    • 原生 JDBC:必要时可转换为 java.sql.Date,但应尽早转换
  3. 老系统迁移建议

    • 逐步替换 DateInstantLocalDateTime
    • 封装工具类处理 util.Datesql.Date 转换,减少散落的转换逻辑

💡 简单粗暴一句话:
java.util.Date 是历史包袱,java.sql.Date 是 JDBC 专用工,真·时间操作请认准 java.time


原始标题:java.util.Date vs java.sql.Date