一句话总结
java.sql.Date 是一个过时且设计有缺陷的类,它只表示日期(年、月、日),不包含时间部分(时、分、秒、毫秒),它被设计用于与 JDBC 交互,将 java.util.Date 转换为 SQL DATE 类型。在现代 Java 开发中,应优先使用 java.time 包中的 java.time.LocalDate。

java.sql.Date 的本质与设计初衷
java.sql.Date 继承自 java.util.Date。
public class java.sql.Date extends java.util.Date {
// ... 实现
}
它的主要设计目的是为了与 JDBC 规范兼容,在数据库中,DATE 类型通常只存储日期信息(如 2025-10-27),而不存储时间。
当你从数据库中查询一个 DATE 类型的列时,JDBC 驱动程序会将其映射为 java.sql.Date 对象,同样,当你想将一个日期存入数据库的 DATE 列时,也需要使用 java.sql.Date。
重要提示: 尽管它继承自 java.util.Date,但它不应该被当作一个完整的日期时间对象来使用,它的父类 java.util.Date 中的时间部分(时、分、秒、毫秒)被刻意设置为无效。

关键方法与陷阱
1 创建 java.sql.Date 对象
通常不使用 new java.sql.Date() 构造函数,而是使用静态工厂方法 valueOf(),因为它能更安全地解析字符串。
// 1. 从字符串创建 (推荐格式: yyyy-MM-dd)
String dateString = "2025-10-27";
java.sql.Date sqlDate = java.sql.Date.valueOf(dateString);
System.out.println("从字符串创建: " + sqlDate); // 输出: 2025-10-27
// 2. 从毫秒值创建 (注意:这会丢失时间信息)
java.util.Date utilDate = new java.util.Date(); // 当前完整时间
long millis = utilDate.getTime();
java.sql.Date sqlDateFromMillis = new java.sql.Date(millis);
System.out.println("从毫秒值创建: " + sqlDateFromMillis); // 输出只有日期部分,如 2025-10-27
2 获取日期信息
你可以使用继承自 java.util.Date 的方法来获取年、月、日。
java.sql.Date sqlDate = java.sql.Date.valueOf("2025-10-27");
// 注意:这些方法已经过时,但仍然可用
int year = sqlDate.getYear() + 1900; // getYear() 返回的是自1900年以来的年数,需要+1900
int month = sqlDate.getMonth() + 1; // getMonth() 返回的是0-11,需要+1
int day = sqlDate.getDate(); // getDate() 返回的是1-31的日
System.out.println("年: " + year); // 输出: 年: 2025
System.out.println("月: " + month); // 输出: 月: 10
System.out.println("日: " + day); // 输出: 日: 27
3 最大的陷阱:时间部分是无效的
这是 java.sql.Date 最令人困惑的地方。
java.sql.Date sqlDate = java.sql.Date.valueOf("2025-10-27");
// 尝试获取时间部分
int hours = sqlDate.getHours(); // 总是返回 0
int minutes = sqlDate.getMinutes(); // 总是返回 0
int seconds = sqlDate.getSeconds(); // 总是返回 0
System.out.println("小时: " + hours); // 输出: 小时: 0
System.out.println("分钟: " + minutes); // 输出: 分钟: 0
System.out.println("秒: " + seconds); // 输出: 秒: 0
java.sql.Date 的所有与时间相关的方法都返回0或无效值,它本质上是一个“伪”java.util.Date。

java.sql.Date 与 java.util.Date 的转换
1 java.sql.Date -> java.util.Date
由于是继承关系,可以直接赋值。
java.sql.Date sqlDate = java.sql.Date.valueOf("2025-10-27");
java.util.Date utilDate = sqlDate; // 向上转型,完全合法
System.out.println("转换后的 utilDate: " + utilDate);
// 输出: 转换后的 utilDate: 2025-10-27 00:00:00.0
// 注意:时间部分被清零了!
2 java.util.Date -> java.sql.Date
需要构造一个新的 java.sql.Date 对象,这会丢失原始 java.util.Date 中的所有时间信息。
java.util.Date utilDate = new java.util Date(); // 假设是 2025-10-27 15:30:45.123
java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
System.out.println("原始 utilDate: " + utilDate);
// 输出: 原始 utilDate: Fri Oct 27 15:30:45 CST 2025
System.out.println("转换后的 sqlDate: " + sqlDate);
// 输出: 转换后的 sqlDate: 2025-10-27
// 时间信息 15:30:45.123 完全丢失!
为什么不推荐使用 java.sql.Date?
- 设计缺陷:它继承自
java.util.Date,但又“阉割”了其时间功能,这违反了面向对象的设计原则(is-a 关系),极易造成混淆。 - API 过时:
java.sql.Date中的大部分方法(如getYear(),getMonth())都已被标记为@Deprecated,官方不推荐使用。 - 线程不安全:
java.util.Date及其子类都不是线程安全的。 - 功能不完整:它无法表示时间,也无法处理时区等复杂场景。
现代替代方案:java.time 包 (Java 8+)
从 Java 8 开始,Java 引入了全新的 java.time 包,它提供了更强大、更安全、更易用的日期时间 API,这是处理日期时间的标准方式。
1 对应 java.sql.Date 的类:java.time.LocalDate
LocalDate 表示一个不带时区的日期,完美地替代了 java.sql.Date 的核心功能。
2 在 JDBC 中使用 LocalDate
现代 JDBC (4.2+) 已经原生支持 java.time 类型,无需手动转换。
从数据库读取 DATE 列:
// 假设 rs 是你的 ResultSet 对象
LocalDate localDate = rs.getObject("birth_date", LocalDate.class);
// 或者
// LocalDate localDate = rs.getDate("birth_date").toLocalDate();
向数据库写入 DATE 列:
// 假设 ps 是你的 PreparedStatement 对象
LocalDate birthDate = LocalDate.of(1990, 5, 15);
ps.setObject("birth_date", birthDate);
// 或者
// ps.setDate("birth_date", java.sql.Date.valueOf(birthDate));
3 java.time 的其他重要类
| 类 | 描述 | 替代 java.sql 的什么? |
|---|---|---|
LocalDate |
仅日期,无时间 | java.sql.Date |
LocalTime |
仅时间,无日期 | - |
LocalDateTime |
日期 + 时间,无时区 | java.sql.Timestamp |
ZonedDateTime |
日期 + 时间 + 时区 | - |
Instant |
时间线上的一个瞬时点,与 UTC 时区相关 | java.sql.Timestamp |
总结与最佳实践
| 特性 | java.sql.Date |
java.time.LocalDate (推荐) |
|---|---|---|
| 用途 | JDBC 交互,映射 SQL DATE 类型 |
表示一个日期(年、月、日) |
| 时间部分 | 无效,总是 0 | 不包含时间部分 |
| API | 过时,不安全 | 现代,清晰,线程安全 |
| 可变性 | 可变 | 不可变 (线程安全) |
| JDBC 支持 | 原生支持 (旧版) | 原生支持 (JDBC 4.2+) |
| 创建方式 | valueOf(String) |
of(int, int, int) 或 parse(CharSequence) |
最佳实践建议:
- 在业务逻辑层:永远不要使用
java.sql.Date,统一使用java.time包中的类(如LocalDate,LocalDateTime)。 - 在数据访问层:让 JDBC 驱动程序为你处理转换,直接使用
rs.getObject()和ps.setObject()来读写LocalDate和LocalDateTime。 - 如果必须处理遗留代码:当你从数据库获取数据后,立即将其转换为
LocalDate:java.sql.Date sqlDate = rs.getDate(...); LocalDate localDate = sqlDate.toLocalDate();,当你需要存入数据库时,再进行转换:java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);。
遵循这个原则,你的代码将更加健壮、清晰且易于维护。
