机器的时间是毫秒数,人的时间是文字,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. 常见误区
不是。那是 Date 默认的 toString 格式(英文+时区缩写 CST),数据本身是对的。觉得不好看用 SimpleDateFormat 翻译,别想"修"它。
编译直接报错——ParseException 是受检异常(5.2 的分派结论)。这是你第一次在真实代码里被编译器逼着表态,正好练手。
这反而是对的用法——模板里非字母的部分(汉字、-、空格)会原样输出。要小心的是字母都别乱拼:MMMM 会输出"九月"(英文环境 September),不是数字。
这个 int 计算结果是 259,200,000,还在 int 范围内,这里恰好不炸;但天数一多(比如 30 天 × 长期)容易溢出,养成习惯写 3L * 24 * 60 * 60 * 1000——先变 long 再乘(6.5 讲过的溢出预防套路)。
新类在 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. 自测一下
从 1970-01-01 00:00:00(UTC)到当前时刻的毫秒数(时间戳)。人看不懂,是机器的时间形态,方便做算术。
Date 默认 toString 就是英文格式,不是乱码。用 SimpleDateFormat.format() 翻译成模板要求的字符串。
直接抛 ParseException(分隔符对不上)。属于受检异常(编译时异常)——编译器强制处理,这是它和运行时异常的根本区别。
月份大写 MM,分钟小写 mm。写反了编译不报错(都是合法字母),但输出含义全错——月变成分钟数。这类"编译不管、结果作废"的错最阴险。
好在:不用自己算毫秒数、语义清晰、LocalDate 系列不可变无线程问题。plusDays 返回新对象(不可变脾气),必须接返回值,d.plusDays(3); 单独一句是白调——String/BigDecimal 同款规矩。
8. 下一步学什么
- ☑ 6.7 日期时间格式化类 ✅ —— 已完成:点此复习 —— 翻译官的"管教手册":模式字母完整规矩、SimpleDateFormat 线程安全坑、DateTimeFormatter 新一代对比。
相关笔记
- 第六章 Java API/6.5 BigInteger和BigDecimal类 ← 上一篇:同为「补基础短板」的工具,本节补的是"时间的表达"
- 第六章 Java API/6.3 System类:currentTimeMillis 的毫秒值,本节正式翻译成人话(6.3 钩子收网)
- 第五章 异常/5.2 运行时异常与编译时异常:ParseException = 受检异常实战第一课
- 第六章 Java API/6.2 字符串类:不可变三连的第三位成员(String → BigDecimal → LocalDate)