新日期时间 API

面试官问:"Java 8 的日期时间 API 和旧的 Date 类有什么区别?"

候选人小叶答:"新的 API 更直观?"

面试官追问:"具体有什么区别?"

小叶说:"新的类是不可变的,线程安全?"

面试官追问:"那怎么把新 API 转成 Date 对象?"

小叶答不上来。

【面试官心理】 这道题考查的是候选人对 Java 8 Date-Time API 的理解。能说出不可变性、线程安全、ISO 8601 标准、并且知道新旧 API 转换的候选人,说明有生产经验。

一、旧的 Date 类的问题 🔴

// 旧 API 的问题:
Date date = new Date(2024, 1, 1); // ❌ 月份从 0 开始!
Date date2 = new Date(2024 - 1900, 1, 1); // 需要减 1900

// 可变:不是线程安全的
Date date = new Date();
date.setTime(0); // 可以修改!

// 格式化不是线程安全的
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 多线程使用会出问题!

二、新 API 的核心类 🔴

2.1 LocalDate(日期)

LocalDate date = LocalDate.now(); // 当前日期
LocalDate date2 = LocalDate.of(2024, 6, 15); // 指定日期
LocalDate date3 = LocalDate.parse("2024-06-15"); // 字符串解析

date.getYear();       // 2024
date.getMonth();       // JUNE
date.getMonthValue();  // 6
date.getDayOfMonth();  // 15
date.getDayOfWeek();   // SATURDAY

date.plusDays(1);      // 加一天
date.minusYears(1);   // 减一年
date.withDayOfMonth(1); // 设为月初

2.2 LocalTime(时间)

LocalTime time = LocalTime.now(); // 当前时间
LocalTime time2 = LocalTime.of(14, 30); // 14:30
LocalTime time3 = LocalTime.parse("14:30:00"); // 解析

time.getHour();   // 14
time.getMinute(); // 30
time.getSecond(); // 0

2.3 LocalDateTime(日期时间)

LocalDateTime dt = LocalDateTime.now();
LocalDateTime dt2 = LocalDateTime.of(2024, 6, 15, 14, 30);

// 组合
LocalDate date = LocalDate.of(2024, 6, 15);
LocalTime time = LocalTime.of(14, 30);
LocalDateTime dt3 = LocalDateTime.of(date, time);

// 互转
LocalDate d = dt.toLocalDate();
LocalTime t = dt.toLocalTime();

2.4 Instant(时间戳)

Instant now = Instant.now(); // UTC 时间戳
Instant epoch = Instant.EPOCH; // 1970-01-01 00:00:00 UTC

Instant later = now.plusSeconds(3600); // 加一小时
long epochSeconds = now.getEpochSecond(); // 秒
long nano = now.toEpochMilli(); // 毫秒

2.5 ZonedDateTime(带时区)

ZonedDateTime zdt = ZonedDateTime.now(); // 当前时区
ZonedDateTime zdt2 = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime zdt3 = ZonedDateTime.parse("2024-06-15T14:30:00+08:00[Asia/Shanghai]");

// 转换时区
ZonedDateTime utc = zdt.withZoneSameInstant(ZoneId.of("UTC"));

三、核心特性 🔴

3.1 不可变性

// ✅ 新 API 是不可变的
LocalDate date = LocalDate.of(2024, 6, 15);
LocalDate tomorrow = date.plusDays(1);
// date 还是 2024-06-15,tomorrow 是 2024-06-16

// ✅ 线程安全
LocalDate date = LocalDate.now(); // 共享无需同步

3.2 格式化与解析

// 使用 DateTimeFormatter
LocalDate date = LocalDate.of(2024, 6, 15);

// 预定义格式
String s1 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // "2024-06-15"

// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String s2 = date.format(formatter); // "2024/06/15"

// 解析
LocalDate parsed = LocalDate.parse("2024/06/15", formatter);

// ✅ DateTimeFormatter 是线程安全的!
// SimpleDateFormat 不是线程安全的!

四、与旧 API 的转换 🟡

// Date → LocalDateTime
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

// LocalDateTime → Date
LocalDateTime ldt2 = LocalDateTime.now();
Instant instant2 = ldt2.atZone(ZoneId.systemDefault()).toInstant();
Date date2 = Date.from(instant2);

// Timestamp → LocalDateTime
Timestamp ts = new Timestamp(System.currentTimeMillis());
LocalDateTime ldt3 = ts.toLocalDateTime();

// LocalDateTime → Timestamp
LocalDateTime ldt4 = LocalDateTime.now();
Timestamp ts2 = Timestamp.valueOf(ldt4);

五、Duration 与 Period 🟡

// Duration:时间间隔(秒、纳秒)
Duration d1 = Duration.ofHours(2);
Duration d2 = Duration.between(start, end);

// Period:日期间隔(年、月、日)
Period p = Period.ofYears(1).plusMonths(2).plusDays(15);

// 计算差异
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 6, 15);
long days = ChronoUnit.DAYS.between(start, end); // 166 天
Period period = Period.between(start, end); // P5M14D

六、生产避坑 🟡

6.1 数据库日期映射

// MyBatis
@Select("SELECT * FROM orders WHERE created_at = #{date}")
List<Order> findByDate(@Param("date") LocalDate date);

// JPA
@Entity
public class Order {
    @Column(name = "created_at")
    private LocalDateTime createdAt; // 直接映射
}

6.2 JSON 序列化

// Jackson 需要配置模块
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

// 或 Spring Boot 2.0+ 自动支持

七、追问升级

面试官:"Java 8 Date-Time API 为什么要设计成不可变?"

// 原因:
// 1. 线程安全:日期时间对象经常被共享
// 2. 可预测性:修改不会影响其他引用
// 3. 更好的 API 设计:plus/minus/with 方法返回新实例
// 4. 函数式友好:可用于 Stream、Lambda