1. Java传统日期时间API概述
如果你在2014年之前写过Java程序,肯定对java.util.Date这个类不陌生。那时候我们要获取当前时间,通常会这样写:
Date now = new Date(); System.out.println(now); // 输出:Tue Jun 25 14:30:45 CST 2024这个输出看起来还算直观,但当你真正开始处理日期计算、格式化等操作时,就会发现这套API设计得非常反人类。我在早期项目中踩过不少坑,比如:
- 月份从0开始计数(1月是0,12月是11)
- 年份从1900年开始计算
- Date对象既包含日期又包含时间,但时间处理非常麻烦
- 线程安全问题让人头疼
后来JDK 1.1引入了Calendar和DateFormat试图解决这些问题,但它们又带来了新的复杂性。直到Java 8才推出了全新的java.time包,官方也正式将Date/Calendar标记为"不推荐使用"。
2. Date类:简单但问题重重
2.1 Date的基本用法
Date类最常用的两个构造方法:
// 无参构造:获取当前时间 Date now = new Date(); // 带long参数构造:根据毫秒数创建日期 Date specificTime = new Date(0L); // 1970-01-01 08:00:00 (东八区)这里有个坑:Date的toString()输出格式是固定的英文格式,而且时区显示为CST(China Standard Time),这在需要本地化显示时会很麻烦。
2.2 常见陷阱
陷阱1:年份计算
Date date = new Date(124, 5, 25); // 2024年6月25日注意这里的年份参数124表示2024(1900+124),月份参数5表示6月(0-based)。这种反直觉的设计很容易出错。
陷阱2:可变性
Date date = new Date(); long time = date.getTime(); date.setTime(time + 1000); // 修改了原始对象Date对象是可变的,这在多线程环境下非常危险。我在一个Web项目中就遇到过因为共享Date对象导致的线程安全问题。
3. DateFormat:格式化与解析
3.1 SimpleDateFormat使用
由于Date的toString()格式固定,我们通常用SimpleDateFormat来格式化:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String formatted = sdf.format(new Date()); // 输出:2024-06-25 14:30:453.2 线程安全问题
重要警告:SimpleDateFormat不是线程安全的!下面的代码在多线程环境下会出问题:
// 错误的共享实例写法 private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd"); // 正确做法是每个线程创建独立实例,或者使用ThreadLocal private static final ThreadLocal<SimpleDateFormat> threadLocalSdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));我曾经在Spring MVC项目中使用共享的SimpleDateFormat实例,结果在高并发时出现了日期解析错乱的问题。
3.3 解析严格性
默认情况下,SimpleDateFormat的解析是宽松的:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = sdf.parse("2024-02-30"); // 会自动转换为2024-03-01如果希望严格校验日期有效性,需要设置:
sdf.setLenient(false); // 这时解析"2024-02-30"会抛出ParseException4. Calendar类:更强大的日期操作
4.1 Calendar基础
Calendar解决了Date的一些设计缺陷,提供了更丰富的日期字段操作:
Calendar cal = Calendar.getInstance(); int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH) + 1; // 仍然要+1 int day = cal.get(Calendar.DAY_OF_MONTH);4.2 日期计算
Calendar的add方法可以方便地进行日期加减:
Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, 7); // 加7天 cal.add(Calendar.MONTH, -1); // 减1个月4.3 时区处理
Calendar比Date更好的时区支持:
Calendar cal = Calendar.getInstance(); cal.setTimeZone(TimeZone.getTimeZone("America/New_York"));5. 新旧API对比与迁移建议
虽然Java 8已经推出多年,但很多遗留系统仍然在使用这些传统API。以下是迁移建议:
Date → Instant
// 旧代码 Date date = new Date(); // 新代码 Instant instant = Instant.now();Calendar → ZonedDateTime
// 旧代码 Calendar cal = Calendar.getInstance(); // 新代码 ZonedDateTime zdt = ZonedDateTime.now();SimpleDateFormat → DateTimeFormatter
// 旧代码 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 新代码 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
如果你正在维护老系统,理解这些传统API的设计缺陷和正确用法仍然非常重要。特别是在面试中,面试官经常会考察你对这些历史问题的理解程度。