在 Java 中,将 Date 对象转换为 String 是一个非常常见的操作,随着 Java 版本的演进,推荐的方法也在不断变化。

下面我将从传统方法到现代最佳实践,为你详细讲解如何进行转换。
核心思路
转换的核心思想是:
- 定义一个格式:你希望最终的字符串长什么样?
yyyy-MM-dd HH:mm:ss或MM/dd/yyyy。 - 使用格式化工具:将
Date对象和格式结合起来,生成符合格式的字符串。
使用 SimpleDateFormat (传统方法,Java 8 以前)
这是最经典、最广为人知的方法。SimpleDateFormat 是一个具体的类,用于格式化和解析日期。
基本用法
SimpleDateFormat 的构造函数接收一个模式字符串,用来定义日期和时间的格式。

常用模式字符:
| 字符 | 含义 | 示例 |
| :--- | :--- | :--- |
| y | 年 | yyyy (2025), yy (23) |
| M | 月 | MM (07), M (7) |
| d | 日 | dd (09), d (9) |
| H | 时 (24小时制) | HH (15), H (15) |
| h | 时 (12小时制) | hh (03), h (3) |
| m | 分 | mm (05), m (5) |
| s | 秒 | ss (09), s (9) |
| S | 毫秒 | SSS (009) |
| E | 星期 | E (星期三), EEEE (Wednesday) |
| a | 上/下午 | a (下午) |
代码示例
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateToStringSimpleDateFormat {
public static void main(String[] args) {
// 1. 获取当前的 Date 对象
Date now = new Date();
System.out.println("原始 Date 对象: " + now);
// 2. 创建 SimpleDateFormat 对象,并指定格式
// 格式: 年-月-日 时:分:秒
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 3. 调用 format() 方法进行转换
String formattedDateTime = formatter.format(now);
System.out.println("格式化后的字符串: " + formattedDateTime);
// --- 其他格式示例 ---
SimpleDateFormat formatter2 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
String formattedDateTimeCN = formatter2.format(now);
System.out.println("中文格式: " + formattedDateTimeCN);
SimpleDateFormat formatter3 = new SimpleDateFormat("MM/dd/yyyy hh:mm a");
String formattedDateTimeUS = formatter3.format(now);
System.out.println("美式格式: " + formattedDateTimeUS);
}
}
⚠️ 重要注意事项:线程安全问题
SimpleDateFormat 是非线程安全的,如果在多线程环境下共享同一个 SimpleDateFormat 实例,可能会导致数据错乱或程序异常。
错误用法 (多线程环境):
// 不要在多线程中这样使用!
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public void parseDate(String dateStr) throws ParseException {
// 多个线程同时调用此方法会出问题
Date date = sdf.parse(dateStr);
// ...
}
解决方案:
-
每次创建新实例 (不推荐,性能开销大):
// 每次调用都创建一个新的对象,效率低 String formatted = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); -
使用
synchronized同步块 (可以,但影响性能):private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); public String format(Date date) { synchronized (sdf) { return sdf.format(date); } } -
使用
ThreadLocal(推荐,性能好且安全):private static final ThreadLocal<SimpleDateFormat> threadLocalFormatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public String format(Date date) { return threadLocalFormatter.get().format(date); }
使用 java.time.format.DateTimeFormatter (现代方法,Java 8+)
自 Java 8 引入了全新的日期时间 API (java.time),这是目前官方推荐的最佳实践,它解决了旧 API 的所有问题,包括线程安全。
基本用法
- 如果你有一个旧的
java.util.Date对象,需要先将其转换为新的java.time类型,最简单的是转换为Instant。 - 然后使用
DateTimeFormatter进行格式化。
代码示例
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateToStringJava8 {
public static void main(String[] args) {
// 1. 获取当前的 Date 对象
Date now = new Date();
System.out.println("原始 Date 对象: " + now);
// 2. 将 java.util.Date 转换为 java.time.Instant
Instant instant = now.toInstant();
// 3. 将 Instant 转换为 ZonedDateTime (带时区)
// 使用系统默认时区
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
// 4. 创建 DateTimeFormatter 并指定格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 5. 使用 format() 方法进行转换
String formattedDateTime = zonedDateTime.format(formatter);
System.out.println("格式化后的字符串: " + formattedDateTime);
// --- 其他格式示例 ---
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE");
String formattedDateTimeCN = zonedDateTime.format(formatter2);
System.out.println("中文格式: " + formattedDateTimeCN);
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a z");
String formattedDateTimeUS = zonedDateTime.format(formatter3);
System.out.println("美式带时区格式: " + formattedDateTimeUS);
}
}
DateTimeFormatter 的优势
- 线程安全:
DateTimeFormatter是不可变的,因此是线程安全的,可以在多个线程间安全地共享同一个实例。 - 更丰富的 API:
java.timeAPI 设计更优秀,功能更强大。 - 时区处理:对时区的支持比旧版 API 更好。
总结与对比
| 特性 | SimpleDateFormat (旧版) |
DateTimeFormatter (新版, Java 8+) |
|---|---|---|
| 所属包 | java.text |
java.time.format |
| 线程安全 | ❌ 非线程安全 | ✅ 线程安全 |
| API 设计 | 功能有限,API 有些混乱 | API 设计清晰、一致、强大 |
与 Date 交互 |
直接作为核心类使用 | 需要通过 Instant 等中间类型转换 |
| 推荐度 | 不推荐用于新项目 | 强烈推荐,是现代 Java 的标准 |
最佳实践建议
-
对于新项目 (Java 8 及以上):始终优先使用
java.timeAPI。- 将
java.util.Date转为Instant,再转为ZonedDateTime或OffsetDateTime,然后用DateTimeFormatter格式化。 - 如果可能,尽量避免在代码中使用
java.util.Date和Calendar。
- 将
-
对于维护旧项目 (Java 7 或更低):如果必须使用
SimpleDateFormat,请务必注意线程安全问题,最佳实践是使用ThreadLocal来为每个线程维护一个独立的实例。 -
JDBC 交互:在与数据库交互时,尽量使用 JDBC 4.2+ 提供的
java.time类型(如TIMESTAMP WITH TIME ZONE),可以直接与OffsetDateTime或ZonedDateTime映射,避免在Date和String之间频繁转换。
