1. 概述
本文将深入对比 Java 中两个历史悠久的日期类:java.util.Date
和 java.sql.Date
。
虽然它们名字相似,继承关系也看似简单,但在实际开发中极易“踩坑”。通过本文,你将清晰掌握:
- 各自的设计目的和使用场景
- 关键差异与潜在陷阱
- 现代 Java 开发中应如何正确处理时间
✅ 核心结论先行:
除非维护老项目,否则应优先使用 Java 8 引入的 java.time
包(如 LocalDate
, Instant
等),避免使用 java.util.Date
和 java.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 |
推荐程度 | ❌ 不推荐 | ⚠️ 仅限数据库交互 |
✅ 正确姿势
新项目一律使用
java.time
包- 日期:
LocalDate
- 时间戳:
Instant
- 带时区:
ZonedDateTime
- 日期:
与数据库交互时
- JPA/Hibernate:直接使用
LocalDate
映射 DATE 字段 - 原生 JDBC:必要时可转换为
java.sql.Date
,但应尽早转换
- JPA/Hibernate:直接使用
老系统迁移建议
- 逐步替换
Date
为Instant
或LocalDateTime
- 封装工具类处理
util.Date
↔sql.Date
转换,减少散落的转换逻辑
- 逐步替换
💡 简单粗暴一句话:
java.util.Date
是历史包袱,java.sql.Date
是 JDBC 专用工,真·时间操作请认准java.time
。