我们来详细解析 java.sql.Date和 java.util.Date的区别、联系以及正确使用方法。
核心结论
-
java.util.Date: 表示一个特定的时间瞬间,精确到毫秒 。它包含日期 和时间两部分信息。是Java中表示日期时间的原始类。 -
java.sql.Date: 是java.util.Date的子类,但它只表示日期 部分(年、月、日),时间部分(时、分、秒、毫秒)被强制设置为00:00:00。它专为与SQL数据库中的DATE类型匹配而设计。
详细对比
| 特性 | java.util.Date |
java.sql.Date |
|---|---|---|
| 包路径 | java.util |
java.sql |
| 继承关系 | 父类 | 子类(extends java.util.Date) |
| 表示内容 | 一个具体的时间点(日期 + 时间 + 毫秒) | 仅日期(年-月-日) |
| 时间部分 | 保留时、分、秒、毫秒 | 被强制设为 00:00:00(对应时区) |
| 设计目的 | 通用的日期时间表示 | 专门用于与SQL数据库的 DATE类型交互 |
| 数据库对应类型 | 无直接对应类型 | SQL DATE(e.g., MySQL DATE, Oracle DATE) |
| 典型用途 | 在Java应用程序内部表示一个完整的时间点 | 在JDBC操作中,作为PreparedStatement的参数或ResultSet的返回值 |
如何正确使用
1. 在JDBC操作中的使用
这是 java.sql.Date最主要的使用场景。JDBC驱动程序知道如何将 java.sql.Date转换为数据库中的 DATE类型,反之亦然。
示例:将生日信息存入数据库
假设数据库表 users中有一个 birthday字段,类型为 DATE。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Date; // 注意:这里导入的是 java.sql.Date
public class JdbcExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database";
String user = "username";
String password = "password";
// 假设我们从用户输入或业务逻辑中得到了一个 java.util.Date 对象
java.util.Date utilDate = new java.util.Date(); // 这里代表当前日期和时间
try (Connection conn = DriverManager.getConnection(url, user, password)) {
String sql = "INSERT INTO users (name, birthday) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "张三");
// 关键步骤:将 java.util.Date 转换为 java.sql.Date
// 使用 java.sql.Date 的构造函数,传入毫秒数
java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
pstmt.setDate(2, sqlDate); // 正确:使用 setDate 方法
pstmt.executeUpdate();
System.out.println("数据插入成功!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
从数据库读取:
// ... 连接数据库的代码 ...
String sql = "SELECT birthday FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
// 从ResultSet中直接获取 java.sql.Date
java.sql.Date sqlBirthday = rs.getDate("birthday");
// 如果需要转换为 java.util.Date,因为继承关系,可以直接赋值
java.util.Date utilBirthday = sqlBirthday; // 多态,向上转型
System.out.println(utilBirthday); // 输出类似:Thu Nov 07 00:00:00 CST 2024
// 注意:时间部分会是 00:00:00
}
2. 类型转换
java.util.Date-> java.sql.Date
这是最常见的转换需求。不能直接强制转换,必须通过毫秒值来构造。
java.util.Date utilDate = new java.util.Date();
// 正确方式:使用毫秒数构造函数
java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
// 错误方式:强制转换 (编译可能通过,但运行时行为不确定,强烈不建议!)
// java.sql.Date badSqlDate = (java.sql.Date) utilDate;
java.sql.Date-> java.util.Date
由于继承关系,这是安全的,可以直接赋值。
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
java.util.Date utilDate = sqlDate; // 自动向上转型,总是安全
重要注意事项和常见陷阱
-
时间信息丢失 : 当你将一个包含非零时间的
java.util.Date转换为java.sql.Date时,时间部分(时、分、秒、毫秒)会永久丢失。转换后,时间永远是00:00:00。 -
不要混淆
setDate和setTimestamp:-
如果你的数据库字段是
DATE类型,使用PreparedStatement.setDate(),参数是java.sql.Date。 -
如果你的数据库字段是
DATETIME或TIMESTAMP类型,你应该使用PreparedStatement.setTimestamp(),参数是java.sql.Timestamp(它是另一个子类,保留了纳秒精度)。
-
-
时区问题 : 虽然
java.sql.Date内部存储的是自1970年1月1日00:00:00 GMT以来的毫秒数,但当调用toString()方法时,它会使用JVM的默认时区来格式化为YYYY-MM-DD的形式。这有时会导致令人困惑的结果,比如在GMT+8时区,1970-01-01 08:00:00 GMT会被显示为1970-01-01。
现代最佳实践:使用 Java 8 的 Date/Time API
java.util.Date和 java.sql.Date都是旧的API,存在设计缺陷(如可变性、线程不安全、API难用等)。在现代Java开发中(Java 8及以上),强烈推荐使用 java.time包下的新日期时间API。
| 旧类 | 新API (java.time) | 说明 |
|---|---|---|
java.util.Date |
Instant, LocalDateTime, ZonedDateTime |
表示时间点或带时区的日期时间 |
java.sql.Date |
LocalDate |
仅表示日期,完美对应SQL DATE |
在JDBC中使用新API(需要JDBC 4.2及以上驱动支持):
// 写入数据库
LocalDate localDate = LocalDate.of(1990, 1, 1);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (birthday) VALUES (?)");
pstmt.setObject(1, localDate); // 直接使用 setObject
pstmt.executeUpdate();
// 从数据库读取
ResultSet rs = pstmt.executeQuery("SELECT birthday FROM users");
if (rs.next()) {
// 直接使用 getObject 转换为 LocalDate
LocalDate birthday = rs.getObject("birthday", LocalDate.class);
System.out.println("生日: " + birthday); // 输出: 1990-01-01
}
如果你的JDBC驱动版本较老,不支持直接转换,可以借助 java.sql.Date作为桥梁:
// LocalDate -> java.sql.Date
LocalDate localDate = LocalDate.now();
java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);
// java.sql.Date -> LocalDate
java.sql.Date sqlDateFromDB = rs.getDate("birthday");
LocalDate localDate = sqlDateFromDB.toLocalDate();
总结
-
根本区别 :
java.util.Date是日期+时间,java.sql.Date是仅日期(是前者的子类)。 -
核心用途 :
java.sql.Date专为与数据库DATE类型交互而设计。 -
正确使用 :在JDBC中,用
PreparedStatement.setDate和ResultSet.getDate来操作java.sql.Date。 -
转换方法 :通过
getTime()方法获得的毫秒数在两者间安全转换。 -
现代实践 :优先使用
java.timeAPI(如LocalDate),它更安全、更清晰、功能更强大。只有在必须与旧API交互时,才考虑使用旧的日期类。