首页
学习
活动
专区
圈层
工具
发布

Java 中 java.util.Date 与 java.sql.Date 有什么区别?

Java 里的java.util.Date和java.sql.Date绝对是那种看起来不起眼但能搞得你 Debug 到半夜的“坑王”。

我们先从表面上看,java.sql.Date是继承自java.util.Date的,按理说是“子承父业”,兼容没问题对吧?但实际上,这俩压根儿不是一个世界的。java.util.Date是 Java 标准库里提供的“通用型”时间类,而java.sql.Date则是 JDBC 里为了数据库操作特化出来的版本,核心区别就在于:一个有时间,一个没时间

就是这么沙雕的区别,java.sql.Date把时间部分(小时、分钟、秒)全都砍了个干净,只保留年月日。为什么这么干?因为很多数据库的DATE类型本来就只存年月日,比如 MySQL 的DATE字段。所以java.sql.Date就跟着这个“传统艺能”走了,给你个精确到天的“日子”。

来看段代码你就明白了:

public class DateExample {

  public static void main(String[] args) {

      java.util.Date utilDate = new java.util.Date();

      java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());

      System.out.println("utilDate: " + utilDate); // 有时间

      System.out.println("sqlDate: " + sqlDate);   // 只有年月日

  }

}

输出可能是这样的:

utilDate: Thu Jun 05 15:30:12 CST 2025

sqlDate: 2025-06-05

你看,时间部分直接没了!这就导致很多时候你把java.sql.Date传回前端,前端一看没时间,直接懵了...

我遇到过最离谱的是,有个哥们把java.sql.Date强转成java.util.Date,然后还奇怪为啥结果有问题。讲真,这是个巨坑。虽然它们在继承关系上确实可以强转,但你转完之后,它的“灵魂”还是那个没有时间的sql.Date

所以你如果真的要拿java.sql.Date去做一些带时间的运算,比如和当前时间比较,或者转换成时间戳,那基本就是拿刀割自己手指头。比如这个例子:

java.sql.Date sqlDate = java.sql.Date.valueOf("2025-06-05");

long millis = sqlDate.getTime(); // 你以为这是某个时间点?

Date converted = new Date(millis);

System.out.println(converted); // 时间是 00:00:00 啊兄弟!

你以为你得到了“某一天的某个时刻”,结果其实是“那天的零点”... 这还不如直接用LocalDate呢。

对,说到这个就得扯一下 Java 8 的时间新 API——java.time.*。这波更新真的可以说是“Date 的反攻”。像LocalDate和LocalDateTime就分别对应java.sql.Date和java.util.Date的干净替代品,前者精确到天,后者精确到秒,而且线程安全,没有时区困扰,API 超清晰,看着就让人舒服🥰。

LocalDate localDate = LocalDate.now();

LocalDateTime localDateTime = LocalDateTime.now();

System.out.println("LocalDate: " + localDate);       // 2025-06-05

System.out.println("LocalDateTime: " + localDateTime); // 2025-06-05T15:45:30

最重要的是,java.sql.Date、java.sql.Time和java.sql.Timestamp都有对应的toLocalDate()或toLocalDateTime()方法,基本可以无痛迁移。

但你要说现实项目里能不能彻底抛弃java.sql.Date?我只能说:想多了。只要你在用 JDBC,尤其是用 MyBatis 或 Hibernate 这种 ORM 框架,它们底层还是会默认你搞点java.sql.Date出来。所以我个人的策略是:入库出库都用LocalDate/LocalDateTime,中间统一转换

比如用 MyBatis 的 TypeHandler 机制,写一个LocalDate转java.sql.Date的 Handler:

@MappedTypes(LocalDate.class)

public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {

  @Override

  public void setNonNullParameter(PreparedStatement ps, int i,

              LocalDate parameter, JdbcType jdbcType) throws SQLException {

      ps.setDate(i, Date.valueOf(parameter));

  }

  @Override

  public LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {

      Date date = rs.getDate(columnName);

      return date != null ? date.toLocalDate() : null;

  }

  // 其他两个 get 方法略

}

配上 XML 或注解,就能实现LocalDate的透明持久化,再也不用在每个 Entity 里扔一堆类型转换的代码了

另外顺带一提,还有不少人会问:“那java.sql.Timestamp是啥角色?”很简单,它就是java.util.Date+ 纳秒精度的变种,主要是数据库里有精确到秒甚至微秒的TIMESTAMP字段时用的。但我一般都尽量避免用它,因为那玩意儿坑更多,特别是跨数据库还经常有精度兼容性问题(MySQL 精确到毫秒,Oracle 精确到纳秒,一导出 CSV 就炸了...)

回到最初的问题:到底java.util.Date和java.sql.Date有啥区别?

如果用一句话总结就是:

java.util.Date是全能选手,啥时间信息都有; java.sql.Date是简化版,为了和数据库的DATE类型打配合,砍掉了时间部分,只保留“日期”。

但这区别,常常在你不注意的时候就搞事,所以我现在干脆上来就用LocalDate和LocalDateTime,实在不行中间过渡一下,也总比在项目上线前踩坑强

不知道你们是不是也在项目中被这俩“日期兄弟”折腾过?欢迎留言交流,毕竟这个坑,谁踩谁知道...

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。

东哥作为一名超级老码农,整理了全网最全《Java高级架构师资料合集》。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O9GBL2jAQK2mdNGu4j-w4vLA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券