☕ 第六章 Java API
📝 一句话概括

机器的时间是毫秒数,人的时间是文字,Date + SimpleDateFormat 就是中间的两级翻译 —— 这正是 6.3 说的「currentTimeMillis() 给的数字不是日期」的正式解法。

6.6 日期和时间类

1. 为什么需要专门的日期类?

6.3 秒表计时用的是 System.currentTimeMillis()——一串大数字:

long now = System.currentTimeMillis();
System.out.println(now);   // 1737244800000 —— 这是"现在"?谁看得懂!

这串数字是「从 1970 年 1 月 1 日 00:00:00(格林尼治时间)到现在过了多少毫秒」,叫时间戳。它有两个性格:

形态 谁的时间 擅长 不擅长
long 毫秒值 机器的时间 算术(比大小、加减) 人看不懂
格式化字符串 "2026-09-19" 人的时间 好看好认 没法做算术

所以需要一个中间角色把两者翻译来翻译去——这就是本节的三件套。

2. Date:Java 的时间代表

import java.util.Date;

Date d1 = new Date();                    // 当前时刻
System.out.println(d1);                  // Sat Sep 19 15:04:31 CST 2026 —— 不是乱码!是默认英文格式
long time = d1.getTime();                // 拆出毫秒值(和 currentTimeMillis() 一个东西)
Date d2 = new Date(1737244800000L);      // 由毫秒值造出对应的时刻

关键认知:Date 对象内部其实就藏着一个毫秒值,getTime() 只是把它取出来。Date 本身没有变魔术,它只是给毫秒值套了件「日期」外衣。

⚠️ 三个坑,见招拆招

坑① 过时构造:new Date(2026, 8, 19) 这种构造方法在 IDEA 里会被划横线(表示已过时),而且年份从 1900 算起、月份从 0 算起(写 8 其实是 9 月!)——两头骗人,别用。 坑② 打印不是乱码:Sat Sep 19... 看着难受,但它不是 bug,是 Date 的默认 toString 格式。想好看,交给下一节的翻译官。 坑③ parse 抛受检异常:解析字符串时格式对不上就炸,且编译器会当场逼你写预案——5.2 说的「Unhandled exception」提示,本尊在此。

3. SimpleDateFormat:格式翻译官

import java.text.SimpleDateFormat;
import java.util.Date;

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(new Date());          // format:Date → 字符串
System.out.println(s);                      // 2026-09-19 15:04:31

Date d = sdf.parse("2026-01-01 08:30:00");  // parse:字符串 → Date(要处理异常!)

模式字母速查(字母写几位,就按几位补零):

字母 含义 示例
yyyy 年 2026
MM 月(MM 补零,M 不补) 09
dd 日 19
HH 时(24 小时制) 15
mm 分 04
ss 秒 31
💡 大小写别写混

月份是大写 MM,分钟是小写 mm;小时用大写 HH(24 小时制)——hh 是 12 小时制,下午 3 点会显示成 03。新手最容易在 MM 和 mm 上翻车。

parse 的用法(受检异常实战):

import java.text.ParseException;

public class Demo06 {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date d = sdf.parse("2026-09-19");     // 格式必须和模板一模一样
        System.out.println(sdf.format(d));    // 2026-09-19 —— 走了个来回
    }
}

注意:parse 要求字符串和模板严格对上(yyyy-MM-dd 就喂 2026-09-19,喂 2026/09/19 直接炸)。

4. 日期加减:老方法 vs 新方法

老方法(Date + 毫秒数做算术)——「三天后是几号」:

long threeDays = 3L * 24 * 60 * 60 * 1000;   // 三天的毫秒数
Date d = new Date(System.currentTimeMillis() + threeDays);
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(d));

原理:毫秒值能做算术(这是它唯一的强项),加 3 天的毫秒数再翻译回字符串。

新方法(JDK8 的 java.time,会认即可):

import java.time.LocalDate;

LocalDate d = LocalDate.now();     // 2026-09-19
LocalDate later = d.plusDays(3);   // 2026-09-22 —— 三天后,一行搞定
System.out.println(later);
Date 老类 java.time 新类
诞生 JDK 1.0 JDK 8
可变性 可变,有线程安全问题 不可变(plusDays 返回新对象)
加减 自己拿毫秒数算 plusDays / minusMonths 直接调
现状 教材主线,还能用 官方推荐
💡 新类的脾气眼熟吗?

不可变、修改必接返回值——和 String(6.2)、BigDecimal(6.5) 一个家族的。Java 核心类里「不可变」的设计哲学反复出现,认准这个脾气,后面学的类会越认越快。

5. 常见误区

⚠️ 误区 1:`Sat Sep 19...` 是乱码/出 bug 了

不是。那是 Date 默认的 toString 格式(英文+时区缩写 CST),数据本身是对的。觉得不好看用 SimpleDateFormat 翻译,别想"修"它。

⚠️ 误区 2:parse 时不写 `throws ParseException` 也不处理

编译直接报错——ParseException 是受检异常(5.2 的分派结论)。这是你第一次在真实代码里被编译器逼着表态,正好练手。

⚠️ 误区 3:`new SimpleDateFormat("2026年MM月dd日")` 里,模板外的汉字要原样写

这反而是对的用法——模板里非字母的部分(汉字、-、空格)会原样输出。要小心的是字母都别乱拼:MMMM 会输出"九月"(英文环境 September),不是数字。

⚠️ 误区 4:毫秒值乘 3 天直接写 `3 * 24 * 60 * 60 * 1000`

这个 int 计算结果是 259,200,000,还在 int 范围内,这里恰好不炸;但天数一多(比如 30 天 × 长期)容易溢出,养成习惯写 3L * 24 * 60 * 60 * 1000——先变 long 再乘(6.5 讲过的溢出预防套路)。

⚠️ 误区 5:以为 LocalDateTime 也叫 Date

新类在 java.time 包里,名字是 LocalDate / LocalTime / LocalDateTime,没有 Date 字样。import 错了(java.sql.Date)会得到行为怪异的类——报错时先看 import 行。

6. 动手试一试(30 秒实验)

新建 Demo06(记得 import java.util.Date; import java.text.SimpleDateFormat; import java.text.ParseException;):

实验 1:打印 new Date() 和 System.currentTimeMillis(),对照着看——一个是英文格式,一个是数字,它们是同一时刻(用 getTime() 验证)。

实验 2:把模板改成 "yyyy/MM/dd HH:mm:ss" 再 format,看输出分隔符的变化——体会模板怎么控制长相。

实验 3:故意 parse("2026.09.19") 而模板是 yyyy-MM-dd,看 ParseException 爆出;再把 main 加上 throws ParseException,编译通过。

实验 4:LocalDate.now().plusDays(3) 打印三天后日期,再用 minusMonths(1) 上个月今天——感受新类的加减有多省事。

7. 自测一下

❓ Q1:`System.currentTimeMillis()` 返回的数字代表什么?人能直接看懂吗?

从 1970-01-01 00:00:00(UTC)到当前时刻的毫秒数(时间戳)。人看不懂,是机器的时间形态,方便做算术。

❓ Q2:Date 打印出来是 `Sat Sep 19 15:04:31 CST 2026`,为什么?怎么变好看?

Date 默认 toString 就是英文格式,不是乱码。用 SimpleDateFormat.format() 翻译成模板要求的字符串。

❓ Q3:`yyyy-MM-dd` 模板下 `parse("2026/09/19")` 会发生什么?这异常属于 5.2 两派中的哪一派?

直接抛 ParseException(分隔符对不上)。属于受检异常(编译时异常)——编译器强制处理,这是它和运行时异常的根本区别。

❓ Q4:SimpleDateFormat 模板里月份和分钟分别怎么写?写反了会怎样?

月份大写 MM,分钟小写 mm。写反了编译不报错(都是合法字母),但输出含义全错——月变成分钟数。这类"编译不管、结果作废"的错最阴险。

❓ Q5:JDK8 的 `d.plusDays(3)` 和 Date 时代"自己加毫秒数"比,好在哪?plusDays 的返回值需要注意什么?

好在:不用自己算毫秒数、语义清晰、LocalDate 系列不可变无线程问题。plusDays 返回新对象(不可变脾气),必须接返回值,d.plusDays(3); 单独一句是白调——String/BigDecimal 同款规矩。

8. 下一步学什么


相关笔记