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

为什么不推荐在 Java 项目中使用 java.util.Date?

昨晚我真是…困得要死还在看线上日志,你们懂吧,就是那种“明明只改了个小功能,怎么时间全乱了”的那种崩溃。起因特别蠢:一个接口返回“创建时间”,前端说同一条记录在列表里是 10:03,点进去详情变成 18:03。我们第一反应:时区呗。结果你猜怎么着,锅居然扣在java.util.Date身上……我当时还嘴硬:Date 不就是个时间点么,能有啥花活。然后脸就被打得啪啪响。

我先说个最常见的坑哈,Date 这个东西它长得像“时间”,但它没把“时区、日历系统、格式化”这些边界说清楚,你一旦把它当“业务时间”用,就会开始乱。比如你要表达“2026-01-31 00:00:00 北京时间”,你用 Date 其实表达的是一个 UTC 时间戳,至于展示成什么,全看你格式化时拿了哪个时区。然后项目里又经常是:本地开发机 GMT+8,测试环境机器可能是 UTC,容器里还可能继承宿主机,反正就是…你以为固定了,其实没固定。

还有个更阴间的:Date 是可变对象。对,可变。你传来传去以为是值,其实是个能被人改的“引用”。我就见过有人为了“省对象”,搞了个 static Date 缓存,结果并发下时间直接串了。类似这种:

import java.util.Date;

public class DateBugDemo {

  // 你看着像常量,其实可变…

  private static final Date SHARED = new Date();

  public static void main(String[] args) throws Exception {

      Thread t1 = new Thread(() -> {

          for (int i = 0; i < 5; i++) {

              SHARED.setTime(System.currentTimeMillis());

              System.out.println("t1 -> " + SHARED.getTime());

              sleep(10);

          }

      });

      Thread t2 = new Thread(() -> {

          for (int i = 0; i < 5; i++) {

              // 业务里可能是“给 Date 加 8 小时”这种骚操作

              SHARED.setTime(SHARED.getTime() + 8L * 3600 * 1000);

              System.out.println("t2 -> " + SHARED.getTime());

              sleep(10);

          }

      });

      t1.start(); t2.start();

      t1.join(); t2.join();

  }

  private static void sleep(long ms) {

      try { Thread.sleep(ms); } catch (InterruptedException ignored) {}

  }

}

你别笑啊,真实项目里就是这样发生的,只是更隐蔽:一个方法拿到 Date 后做setTime、setYear(老 API 还挺多这种),另一个方法以为 Date 还是原来的值,最后落库、发 MQ、打印日志,时间线像被狗啃过一样。

然后是第二波坑:Date 的“周边生态”太容易踩雷。你想格式化吧,很多人会写SimpleDateFormat,这货又是线程不安全的;你放到 static 里复用,跟上面 Date 一组合,直接双倍快乐。你说那我每次 new 一个呗,可以,但你性能又开始抖,线上高并发你就会看到 GC 抽风。反正就是两头不讨好。

第三波坑我觉得更要命:Date 很难表达“业务语义”。业务里经常不是“某个时间点”,而是“某一天”“某个本地时间”“某个时区的截止时间”。比如生日是“1999-05-20”,你用 Date 表示会变成一个带时区的时间点,跨时区一格式化就变前一天/后一天,用户直接来骂你。还有“每天 0 点清算”,你用 Date 表示那个 0 点,是哪个 0 点?夏令时那天甚至可能没有 02:00,或者重复一次,反正挺恶心的。

所以现在我在 Java 项目里基本就一句话:能不用java.util.Date就别用,除非你是跟遗留接口对接不得不用。新代码优先上java.time(JDK8+ 那套),它把“时间点/本地日期/本地时间/时区”分得很清楚,而且大多数类是不可变的,天然少很多坑。

举个你们天天写的场景:接口传入“开始时间、结束时间”,后端要查库。以前很多人 Date 一把梭:

// 旧写法的味道…你懂的

Date start = ...;

Date end = ...;

// 然后各种 + 24h - 1ms 之类的骚操作

换成java.time我一般这么写,语义清楚点:

import java.time.*;

import java.time.format.DateTimeFormatter;

public class TimeGoodDemo {

  private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");

  private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

  public static void main(String[] args) {

      // 业务上就是“某天”

      LocalDate day = LocalDate.parse("2026-01-31");

      // 清算区间:当天 00:00:00 到 23:59:59(注意:更严谨可用 [start, nextDayStart))

      ZonedDateTime start = day.atStartOfDay(ZONE);

      ZonedDateTime nextDayStart = day.plusDays(1).atStartOfDay(ZONE);

      System.out.println("start=" + start.format(FMT));

      System.out.println("nextDayStart=" + nextDayStart.format(FMT));

      // 真要落库 timestamp(UTC 时间点)就用 Instant

      Instant startInstant = start.toInstant();

      Instant endInstantExclusive = nextDayStart.toInstant();

      System.out.println("startInstant=" + startInstant);

      System.out.println("endInstantExclusive=" + endInstantExclusive);

  }

}

你看这个写法就舒服:LocalDate表示“日历上的一天”,ZonedDateTime表示“带时区的本地时间”,Instant表示“时间戳”。每个东西都有自己的地盘,不会混。

还有个我特别爱用的小技巧:用Clock把“当前时间”从代码里抽出来。你们写测试是不是老在那new Date()、System.currentTimeMillis()然后 Mock 半天?换成这样:

import java.time.*;

public class ClockDemo {

  private final Clock clock;

  public ClockDemo(Clock clock) {

      this.clock = clock;

  }

  public Instant now() {

      return Instant.now(clock);

  }

  public static void main(String[] args) {

      Clock fixed = Clock.fixed(Instant.parse("2026-01-31T00:00:00Z"), ZoneOffset.UTC);

      ClockDemo demo = new ClockDemo(fixed);

      System.out.println(demo.now()); // 测试里稳得一批

  }

}

最后再说一句现实点的:很多框架、库、老接口还是会给你 Date,比如 JDBC、某些 SDK。那也没事,你在边界层做转换就行,别让 Date 在你业务域里乱跑。转换也很直白:

import java.time.Instant;

import java.util.Date;

public class ConvertDemo {

  public static Instant toInstant(Date date) {

      return date == null ? null : date.toInstant();

  }

  public static Date toDate(Instant instant) {

      return instant == null ? null : Date.from(instant);

  }

}

反正我现在的感觉就是:java.util.Date最大的问题不是“它不能用”,而是它太容易让人“以为自己用对了”。一开始看着省事,后面排查线上时间 bug 的时候,你会发现时间这种东西一乱,日志、监控、订单状态、对账,全都跟着乱…你想想那种半夜被拉群里,“为啥昨天的订单跑到今天了”,你还得解释“因为服务器是 UTC”…算了不说了,真顶不住。

对了,有人要是还在项目里到处new Date()当“业务时间”,你就把这段话甩他脸上(别太用力哈),不然迟早一起熬夜。然后…我先去喝口水,眼睛都睁不开了。

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