我将从最现代、推荐的方式开始,然后介绍传统但仍在广泛使用的方式,并解释为什么现代方式更好。

(图片来源网络,侵删)
核心概念:格式化器
无论使用哪种 API,核心思想都是:你不能随意地将一个字符串(如 "2025-10-27 15:30:00")转换成一个日期对象,你必须告诉 Java 你的字符串具体是什么格式,"年-月-日 时:分:秒"。
这个“翻译官”就是格式化器,在 Java 中:
DateTimeFormatter(Java 8+) 用于新的日期时间 API。SimpleDateFormat(旧 API) 用于旧的java.util.Date和java.util.Calendar。
使用 Java 8+ 的 java.time API (强烈推荐)
这是目前 Java 世界中处理日期时间的标准方式,它设计更优秀,线程安全,并且功能更强大。
核心类
LocalDate: 表示一个日期(年-月-日),没有时间部分。LocalTime: 表示一个时间(时-分-秒-纳秒),没有日期部分。LocalDateTime: 表示一个日期和时间(年-月-日-时-分-秒-纳秒)。DateTimeFormatter: 用于格式化和解析日期时间对象。ZonedDateTime: 带有时区的日期时间。Instant: 表示一个时间戳(自 1970-01-01T00:00:00Z 以来的秒/纳秒)。
示例代码
假设我们有字符串 "2025-10-27 15:30:00",想将其转换为 LocalDateTime。

(图片来源网络,侵删)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public StringToDateExampleJava8 {
public static void main(String[] args) {
// 1. 定义你的字符串日期
String dateString = "2025-10-27 15:30:00";
// 2. 定义字符串的格式,必须与字符串的格式完全匹配!
// yyyy: 年, MM: 月, dd: 日, HH: 24小时制, mm: 分, ss: 秒
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
try {
// 3. 使用格式化器进行解析
LocalDateTime localDateTime = LocalDateTime.parse(dateString, formatter);
// 4. 输出结果
System.out.println("原始字符串: " + dateString);
System.out.println("转换后的 LocalDateTime: " + localDateTime);
System.out.println("年份: " + localDateTime.getYear());
System.out.println("月份: " + localDateTime.getMonthValue());
System.out.println("小时: " + localDateTime.getHour());
// --- 如果只需要日期部分 ---
// 将 LocalDateTime 转换为 LocalDate
LocalDate localDate = localDateTime.toLocalDate();
System.out.println("转换后的 LocalDate: " + localDate);
// --- 如果只需要时间部分 ---
// 将 LocalDateTime 转换为 LocalTime
LocalTime localTime = localDateTime.toLocalTime();
System.out.println("转换后的 LocalTime: " + localTime);
} catch (Exception e) {
System.err.println("日期解析失败,请检查格式是否正确!");
e.printStackTrace();
}
}
}
输出:
原始字符串: 2025-10-27 15:30:00
转换后的 LocalDateTime: 2025-10-27T15:30
年份: 2025
月份: 10
小时: 15
转换后的 LocalDate: 2025-10-27
转换后的 LocalTime: 15:30
常用格式模式
| 符号 | 含义 | 示例 |
|---|---|---|
yyyy |
4位数年份 | 2025 |
MM |
2位数月份 | 10 |
dd |
2位数日期 | 27 |
HH |
24小时制的小时 | 15 |
hh |
12小时制的小时 | 03 |
mm |
分钟 | 30 |
ss |
秒 | 00 |
SSS |
毫秒 | 500 |
X |
时区偏移 (e.g., +08, +0800, Z) | +08:00 |
使用旧版 java.util.Date 和 SimpleDateFormat
在 Java 8 之前,这是唯一的方式。最大的缺点是 SimpleDateFormat 是非线程安全的,在多线程环境下使用必须非常小心,否则会导致数据错误或程序异常。
核心类
java.util.Date: 表示一个特定的瞬间,精确到毫秒。java.text.SimpleDateFormat: 用于格式化和解析Date对象。
示例代码
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public StringToDateExampleLegacy {
public static void main(String[] args) {
// 1. 定义你的字符串日期
String dateString = "2025-10-27 15:30:00";
// 2. 定义字符串的格式
// 注意:SimpleDateFormat 是非线程安全的!不要在类级别声明它。
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
// 3. 使用格式化器进行解析
Date date = formatter.parse(dateString);
// 4. 输出结果
System.out.println("原始字符串: " + dateString);
System.out.println("转换后的 java.util.Date: " + date);
// Date.toString() 的输出格式是英文的, Fri Oct 27 15:30:00 CST 2025
} catch (ParseException e) {
System.err.println("日期解析失败,请检查格式是否正确!");
e.printStackTrace();
}
}
}
输出:
原始字符串: 2025-10-27 15:30:00
转换后的 java.util.Date: Fri Oct 27 15:30:00 CST 2025
SimpleDateFormat 的线程安全问题
SimpleDateFormat 内部持有一个可变的 Calendar 实例,当多个线程同时调用其 parse() 或 format() 方法时,会竞争同一个 Calendar 实例,导致解析错误。
错误的做法 (在多线程环境下):
// 错误!不要在多线程环境下共享同一个 SimpleDateFormat 实例。
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public void parse(String dateStr) {
try {
sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
}
正确的做法 (在多线程环境下):
-
每次创建新实例 (不推荐,性能差):
// 每次调用都创建一个新的,开销大 new SimpleDateFormat("yyyy-MM-dd").parse(dateStr); -
使用
ThreadLocal(推荐的做法):private static final ThreadLocal<SimpleDateFormat> threadLocalSdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public void parse(String dateStr) { try { // 从当前线程的局部变量中获取 threadLocalSdf.get().parse(dateStr); } catch (ParseException e) { e.printStackTrace(); } }
总结与对比
| 特性 | Java 8+ java.time API |
旧版 java.util.Date API |
|---|---|---|
| 核心类 | LocalDate, LocalDateTime, DateTimeFormatter |
Date, SimpleDateFormat |
| 线程安全 | 是。DateTimeFormatter 是不可变的,天生线程安全。 |
否。SimpleDateFormat 是可变的,非线程安全。 |
| API 设计 | 清晰、易用。date.getYear() 比 date.getYear() + 1900 直观得多。 |
笨拙、不直观,很多方法已经过时,且月份从0开始。 |
| 功能 | 更强大,支持时区、持续时间、日期计算等现代功能。 | 功能有限,时区处理等操作非常复杂。 |
| 推荐度 | 强烈推荐,所有新项目都应使用此 API。 | 不推荐,仅用于维护旧代码或与旧系统交互。 |
如何选择?
- 新项目: 毫不犹豫地使用 Java 8+ 的
java.timeAPI,它更安全、更强大、更符合现代编程思想。 - 维护旧代码: 如果项目代码库已经大量使用了
java.util.Date和SimpleDateFormat,为了保持一致性,可以继续使用,但必须注意其线程安全问题(推荐使用ThreadLocal)。 - 混合使用: 如果新代码需要和旧代码交互,
java.timeAPI 提供了转换方法。java.util.Date可以转换为Instant,而Instant又可以转换为java.time中的对象。// java.util.Date -> java.time.Instant -> java.time.LocalDateTime Date oldDate = new Date(); Instant instant = oldDate.toInstant(); LocalDateTime ldt = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
