掌握 java.util.Date 和 java.util.Calendar 的基本用法
熟练使用 SimpleDateFormat 进行日期格式化与解析
理解传统日期时间 API 在格式化方面的缺陷与线程安全问题
Date 与 Calendar早期的日期表示
在 Java 8 之前,开发者主要依赖 java.util.Date 和 java.util.Calendar 处理日期和时间。
Date 表示一个特定的瞬间(精确到毫秒),但其大部分方法已被弃用。
Calendar 是一个抽象类,提供了对年、月、日、时、分、秒等字段的操作能力。
// 使用 Date 获取当前时间
Date now = new Date();
System.out.println("当前时间(Date): " + now);
// 使用 Calendar 获取结构化日期
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 注意:月份从 0 开始!
int day = cal.get(Calendar.DAY_OF_MONTH);
System.out.println("今天是:" + year + " 年 " + month + " 月 " + day + " 日");重要提示:
Calendar.MONTH的值范围是 0(January)到 11(December),这是初学者常犯的错误来源。
SimpleDateFormat的使用
由于 Date.toString() 输出格式固定且不可控,Java 提供了 java.text.SimpleDateFormat 来实现自定义格式化。
格式化:将 Date 转为字符串
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateFormatExample {
public static void main(String[] args) {
Date now = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH:mm:ss");
String formatted = formatter.format(now);
System.out.println("格式化后的时间:" + formatted);
}
}常用模式符号
解析:将字符串转为 Date
try {
String input = "2026 年 01 月 30 日 15:30:00";
SimpleDateFormat parser = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH:mm:ss");
Date parsedDate = parser.parse(input);
System.out.println("解析结果:" + parsedDate);
} catch (ParseException e) {
System.err.println("日期格式不匹配!");
}注意:解析时若字符串与模式不一致,会抛出
ParseException,必须处理异常。
SimpleDateFormat 缺陷SimpleDateFormat 不是线程安全的。在多线程环境中共享同一个实例可能导致解析错误或格式混乱。
// 危险示例:多个线程共用一个 formatter
SimpleDateFormat unsafe = new SimpleDateFormat("yyyy-MM-dd");
// 安全做法 1:每次创建新实例(性能较差)
// 安全做法 2:使用 ThreadLocal
private static final ThreadLocal<SimpleDateFormat> FORMATTER =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));此外,SimpleDateFormat 对无效日期默认“宽容”处理(如 2026-02-30 会被自动调整为 2026-03-02),需调用 setLenient(false) 关闭此行为:
SimpleDateFormat strict = new SimpleDateFormat("yyyy-MM-dd");
strict.setLenient(false); // 严格模式可变性:Date 和 Calendar 可被修改,易引发并发 bug。
API 设计混乱:年份从 1900 开始计算(Date.getYear() 返回 126 表示 2026 年),月份从 0 开始。
格式化工具不安全:SimpleDateFormat 非线程安全,且错误处理机制薄弱。
缺乏时区与国际化深度支持。
SimpleDateFormat 是 Java 8 前唯一的标准格式化工具,但存在线程安全和容错性问题。
使用时务必注意月份从 0 开始、年份偏移等陷阱。
在多线程场景中,应避免共享 SimpleDateFormat 实例,推荐使用 ThreadLocal 或迁移到 Java 8 新 API。
传统日期时间体系已过时,新项目应优先采用 java.time 包。
为什么 SimpleDateFormat 不是线程安全的?其内部哪些状态可能导致并发问题?
如果你在一个高并发 Web 应用中必须使用 SimpleDateFormat,有哪些可行的优化方案?
尝试用 SimpleDateFormat 解析 "2026-02-30",观察结果。如何确保输入日期必须合法?