日期时间对象
关于日期时间的操作可以分为两种:
- 转换:与字符串的互相转换,与时间戳的互相转换
- 计算:计算两个时间点之间的间隔、时间点与时间段的计算(计算下周N、下个月D日、去年M月D日等等)
Java8 提供了三个类:LocalDate、LocalTime、LocalDateTime,它们的形式如 2020-01-01、12:30:00、2020-01-01 12:30:00
创建对象
获取类对象的方法非常非常简单
1 | LocalDate now = LocalDate.now(); |
2 | LocalDate ld = LocalDate.of(2019, 1, 1); |
3 | // 获取年月日 |
4 | now.getYear(); |
5 | now.getMonthValue(); // 如果你调用了 now.getMonth() ,那么它将返回给你一个大写的英文月份单词 |
6 | now.getDayOfMonth(); |
7 | // 顾名应该思义 |
8 | getDayOfWeek(); |
9 | getDayOfYear(); |
10 | |
11 | // 设置年月日 |
12 | LocalDate ld1 = ld.withYear(2021); // 2021-01-01 |
13 | LocalDate ld2 = ld.withMonth(12); // 2019-12-01 |
14 | LocalDate ld3 = ld.withDayOfMonth(12); // 2019-12-12 |
15 | // 你可能会纳闷,既然是设置,为什么不用单词 set 呢,而用 with |
16 | // 因为,set 操作一般是改变调用对象本身,没有返回值; |
17 | // 而 with 是在调用对象基础上另外创建一个新对象,设置好值后返回,没有改变调用对象 |
18 | |
19 | // 如果你是那个打破砂锅的孩子,你可能会问:为什么不能改变调用对象? |
20 | // 因为 LocalDate 是 final 修饰的(final 人称 Java 界的自宫之刀) |
21 | // 从物理的角度来讲,目前人类无法改变时间(穿越) |
22 | |
23 | // 如果你有 ld.withMonth(13) 这种反人类历法的操作,当然是会抛出异常的 |
LocalTime和LocalDateTime都有类似于LocalDate的方法,这里就不一一列举了(因为我感觉自己越来越像 api 文档了)
转换
日期时间对象 和 字符串 之间的互相转换:
1 | // LocalDateTime 对象 -> 字符串 |
2 | DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
3 | LocalDateTime now = LocalDateTime.now(); |
4 | String dateTimeStr = now.format(dtf); |
5 | System.out.println(dateTimeStr); |
6 | |
7 | // 字符串 -> LocalDateTime 对象 |
8 | String str = "2022-01-30 12:15:20"; |
9 | LocalDateTime dateTime = LocalDateTime.parse(str, dtf); |
10 | System.out.println(dateTime); |
DateTimeFormatter 类还提供一些现成的 formatter ,比如
1 | DateTimeFormatter.BASIC_ISO_DATE ==> DateTimeFormatter.ofPattern("yyyyMMdd") |
2 | DateTimeFormatter.ISO_LOCAL_DATE ==> DateTimeFormatter.ofPattern("yyyy-MM-dd") |
3 | // 更多 formatter 可以 api 文档中查询 |
学习的本质,不在于记住哪些知识,而在于它触发了你的思考。—— 迈克尔·桑德尔
日期时间 和 时间戳 之间的互相转换:
1 | // LocalDateTime 对象 -> 时间戳 |
2 | LocalDateTime now = LocalDateTime.now(); |
3 | // 获取系统默认时区 |
4 | ZoneId systemDefaultZoneId = ZoneId.systemDefault(); |
5 | Instant instant = now.atZone(systemDefaultZoneId).toInstant(); |
6 | long timestamp = instant.toEpochMilli(); |
7 | System.out.println(timestamp); |
8 | |
9 | |
10 | // 时间戳 -> LocalDateTime 对象 |
11 | long timestamp2 = 1578919583784L; |
12 | Instant instant2 = Instant.ofEpochMilli(timestamp2); |
13 | LocalDateTime dateTime2 = LocalDateTime.ofInstant(instant2, systemDefaultZoneId); |
14 | System.out.println(dateTime2); |
“我不明白为什么要把时间戳搞得这么麻烦!”
另外:java.util.Date 与 java.time.LocalDateTime 之间的转换需要通过 Instant 实现,它俩都没有提供直接的转换方法
1 | // 获取系统默认时区 |
2 | ZoneId systemDefaultZoneId = ZoneId.systemDefault(); |
3 | // Date 转为 LocalDateTime |
4 | Date date3 = new Date(); |
5 | Instant instant3 = date3.toInstant(); |
6 | LocalDateTime localDateTime3 = LocalDateTime.ofInstant(instant3, systemDefaultZoneId); |
7 | |
8 | // LocalDateTime 转为 Date |
9 | Instant instant4 = now.atZone(systemDefaultZoneId).toInstant(); |
10 | Date date4 = Date.from(instant4); |
还有:LocalDateTime 可以由 LocalDate 和 LocalTime 组成,也可以拆分成它俩
1 | LocalDate nowLocalDate = LocalDate.now(); |
2 | LocalTime nowLocalTime = LocalTime.now(); |
3 | LocalDateTime nowLocalDateTime = LocalDateTime.of(nowLocalDate, nowLocalTime); |
4 | |
5 | nowLocalDateTime.toLocalDate(); |
6 | nowLocalDateTime.toLocalTime(); |
计算
计算时间点与时间点之间的间隔:
1 | // 计算日期时间之间的间隔 |
2 | LocalTime startTime = LocalTime.now(); |
3 | LocalTime endTime = startTime.plusHours(1).plusMinutes(50); |
4 | Duration duration = Duration.between(startTime, endTime); |
5 | |
6 | // 间隔秒数 |
7 | duration.getSeconds(); |
8 | // 间隔天数 |
9 | duration.toDays(); |
10 | // 间隔小时数 |
11 | duration.toHours(); |
12 | // 间隔分钟数 |
13 | duration.toMinutes(); |
Duration.between(start, end) 的参数可以是 LocalDateTime 、LocalTime
它只会返回一个整数(舍掉小数后的整数,等同于 floor()),不会返回 1小时50分钟 这样的形式
如果你想要 y年M个月d天 H小时m分钟s秒 这种形式,或许你自己动手组装一下了
1 | // 计算日期之间的间隔 |
2 | LocalDate startDate = LocalDate.now(); |
3 | LocalDate endDate = LocalDate.of(2031, 1, 1); |
4 | Period pe = Period.between(startDate, endDate); |
5 | pe.getYears(); |
6 | pe.getMonths(); |
7 | pe.getDays(); |
时间点与时间段的计算:
1 | public LocalDateTime plusYears(long years) { |
2 | LocalDate newDate = date.plusYears(years); |
3 | return with(newDate, time); |
4 | } |
5 | |
6 | public LocalDateTime plusMonths(long months) { |
7 | LocalDate newDate = date.plusMonths(months); |
8 | return with(newDate, time); |
9 | } |
10 | |
11 | public LocalDateTime plusWeeks(long weeks) { |
12 | LocalDate newDate = date.plusWeeks(weeks); |
13 | return with(newDate, time); |
14 | } |
15 | |
16 | public LocalDateTime plusDays(long days) { |
17 | LocalDate newDate = date.plusDays(days); |
18 | return with(newDate, time); |
19 | } |
20 | |
21 | public LocalDateTime plusHours(long hours) { |
22 | return plusWithOverflow(date, hours, 0, 0, 0, 1); |
23 | } |
24 | |
25 | public LocalDateTime plusMinutes(long minutes) { |
26 | return plusWithOverflow(date, 0, minutes, 0, 0, 1); |
27 | } |
28 | |
29 | public LocalDateTime plusSeconds(long seconds) { |
30 | return plusWithOverflow(date, 0, 0, seconds, 0, 1); |
31 | } |
32 | |
33 | public LocalDateTime plusNanos(long nanos) { |
34 | return plusWithOverflow(date, 0, 0, 0, nanos, 1); |
35 | } |
36 | |
37 | public LocalDateTime minusYears(long years) { |
38 | return (years == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-years)); |
39 | } |
40 | |
41 | public LocalDateTime minusMonths(long months) { |
42 | return (months == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-months)); |
43 | } |
44 | |
45 | public LocalDateTime minusWeeks(long weeks) { |
46 | return (weeks == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeks)); |
47 | } |
48 | |
49 | public LocalDateTime minusDays(long days) { |
50 | return (days == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-days)); |
51 | } |
52 | |
53 | public LocalDateTime minusHours(long hours) { |
54 | return plusWithOverflow(date, hours, 0, 0, 0, -1); |
55 | } |
56 | |
57 | public LocalDateTime minusMinutes(long minutes) { |
58 | return plusWithOverflow(date, 0, minutes, 0, 0, -1); |
59 | } |
60 | |
61 | public LocalDateTime minusSeconds(long seconds) { |
62 | return plusWithOverflow(date, 0, 0, seconds, 0, -1); |
63 | } |
64 | |
65 | public LocalDateTime minusNanos(long nanos) { |
66 | return plusWithOverflow(date, 0, 0, 0, nanos, -1); |
67 | } |
看吧,加减年数、月数、天数、小时数、分钟数、秒数、毫秒数都有
想怎么用就怎么用,举个小例子:
1 | LocalDateTime now = LocalDateTime.now(); |
2 | |
3 | // 30年后的今天(我还要上班,还没退休) |
4 | LocalDateTime after30Years = now.plusYears(30L); |
5 | System.out.println(after30Years); |
6 | |
7 | // 347个月后(我就能还清贷款了 ╥﹏╥) |
8 | LocalDateTime after348Months = now.plusMonths(347L); |
9 | System.out.println(after348Months); |
10 | |
11 | // 11天后(就是除夕了) |
12 | LocalDateTime after10Days = now.plusDays(10L); |
13 | System.out.println(after10Days); |
14 | |
15 | // 8小时前(我在上班) |
16 | LocalDateTime before8Hours = now.minusHours(8L); |
17 | System.out.println(before8Hours); |
18 | |
19 | // 3分钟前(我开始听 Let it go 这首歌) |
20 | LocalDateTime before3Before = now.minusMinutes(3L); |
21 | System.out.println(before3Before); |
22 | |
23 | // 10秒前(写下下面这条代码) |
24 | LocalDateTime before10Second = now.minusSeconds(10L); |
25 | System.out.println(before10Second); |
其实不用区分什么加减的,也可以用 plusXxx 做减法,只要传入负数参数就行了
另外这里还有一类需求,比如:
明年的感恩节是哪天?(每年11月的第四个星期四为感恩节)
下周五是哪天?
这就要用到时间校正器 TemporalAdjuster
这里容我先介绍一下 TemporalAdjuster,它是一个函数式接口,只有一个方法 adjustInto
1 | |
2 | public interface TemporalAdjuster { |
3 | Temporal adjustInto(Temporal temporal); |
4 | } |
Temporal是一个接口,LocalDateTime、LocalDate、LocalTime都是它的实现类
在 LocalDateTime 、LocalDate 、 LocalTime 中都有 with(TemporalAdjuster adjuster) 这个方法用来实现上面提到的另类需求。
1 | // 下周五 |
2 | LocalDateTime now = LocalDateTime.now(); |
3 | LocalDateTime nextFriday = now.with(dt -> { |
4 | // dt 是 `Temporal` 对象,但实质上是调用对象的类型 |
5 | LocalDateTime dateTime = (LocalDateTime) dt; |
6 | // 非常可惜,没有 withDayOfWeek() 这个方法,要不然就会非常方便了 |
7 | int dayOfWeekValue = dateTime.getDayOfWeek().getValue(); |
8 | int fridayValue = DayOfWeek.FRIDAY.getValue(); |
9 | return dateTime.plusWeeks(1L) |
10 | .plusDays(fridayValue - dayOfWeekValue); |
11 | }); |
12 | System.out.println(nextFriday); |
13 | |
14 | // 明年的感恩节(明年11月第四个星期四) |
15 | LocalDate thanksGivingDay = LocalDate.now().with( t -> { |
16 | LocalDate d = (LocalDate) t; |
17 | // 明年11月1日 |
18 | LocalDate newDate = d.plusYears(1L).withMonth(11).withDayOfMonth(1); |
19 | |
20 | int dayOfWeekValue = newDate.getDayOfWeek().getValue(); |
21 | int thursdayValue = DayOfWeek.THURSDAY.getValue(); |
22 | long plusWeeks = dayOfWeekValue > thursdayValue ? 4L : 3L; |
23 | return newDate.plusWeeks(plusWeeks) |
24 | .plusDays(thursdayValue - dayOfWeekValue); |
25 | }); |
26 | System.out.println(thanksGivingDay); |
其实 TemporalAdjusters 提供了许多 TemporalAdjuster 对象,就像上一节 Stream 中 Collectors 之于 Collector 一样 。
使用 TemporalAdjusters 能够十分方便的实现上面的需求
1 | // 下个周五 |
2 | LocalDateTime nextFriday = LocalDateTime.now().with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); |
3 | System.out.println(nextFriday); |
4 | |
5 | // 明年的感恩节(明年11月第四个星期四) |
6 | LocalDate date = LocalDate.now().plusYears(1L).withMonth(11); |
7 | LocalDate thanksGivingDay = date.with(TemporalAdjusters.dayOfWeekInMonth(4, DayOfWeek.THURSDAY)); |
8 | System.out.println(thanksGivingDay); |
注意:
TemporalAdjusters.next(DayOfWeek day)方法返回的是 接下来第一个周五,并不是我们一般理解的 下周五,比如说:今天 2020-01-13(周一),那么返回的就是 2020-01-17 四天后的周五。
另外 TemporalAdjusters 并不止提供了上面这2个方法,还有很多其他方法, API 文档 中给出了足够多的例子,一看就明白了。
建议有兴趣的同学,去阅读一些源码,参考 Java8 代码逻辑,然后用其他编程语言实现相同的日期时间操作,因为在其他编程中(比如 javaScript)也会经常用到日期时间的操作
其他特性
罗列出来表示我知道他们,但不表示我理解他们,所以 Let It Go!
接口中的默认方法
接口中的静态方法
Optional 类
1 | Optional<String> op = Optional.of(str); |
它是用来标识这个变量有可能为空。
如果一个变量有可能为空,Java8 之前我们每次使用这个变量时,都必须判断它是否为空。现在也是!!
Optional 并不能避免空指针异常,仅仅是表示标识变量可能为空。打个比方:
前面一条路上埋了地雷,但从表面上完全看不出来,除非我们走一步都扔石头试一下,否则说不准哪一步就炸了。而 Optional就是用来标识地雷位置的,我们知道了哪个位置有雷,就会绕着走,从而能够安全通过
另外 Optional 还提供了一个设置默认值的功能,挺好玩的。
1 | Integer pageSize = null; |
2 | // 以前我们设置默认值 |
3 | //pageSize = pageSize == null ? 10 : pageSize; |
4 | //System.out.println(pageSize); |
5 | |
6 | // 使用 Optional 设置默认值 |
7 | pageSize = Optional.ofNullable(pageSize) |
8 | .orElse(20); |
9 | System.out.println(pageSize); |
10 | |
11 | // 自定义默认值 |
12 | Integer defaultPageSize = Optional.ofNullable(pageSize) |
13 | .orElseGet(() -> { |
14 | return new Integer(50); |
15 | }); |
结语
Java8 新特性系列随便到此就结束了。
最最关键的,还是多看 Java 官方 API 文档!!
我在想可能我们被矫枉过正了。各种各样技术群最多的回答都是:去问百度!! 百度全知道吗?!
说实在的,在今天这个时代,是个人都能在网络上发表文章言论,然后大家再互相转载,假的都能成为真的!
没有经过验证就转载的;在当时有效,现在过时了的;随便在文章中一个转载链接的 … 比比皆是
这里有一个开眼的短视频,介绍 虚假新闻是如何在传播
最恼人的是,百度搜索一个关键词 XXX,点进去一篇博客文章,结果文章内容根本就没有这个关键词,那么关键词到底在哪呢?
关键词在网站的推荐文章列表标题里,所以百度爬取到了,显示在搜索结果里了。真是日了狗了!!
百度搜到的博客能别看就别看,看也要看那些经常更新,比较靠谱的。
靠谱的学习途径:
- 官方最新 API 文档
- 书籍/视频(要注意权威性和时效性)
- 经常更新靠谱的博客
轻易不要百度!!


