Java8 新特性(三) - 日期时间对象以及一些其他特性

日期时间对象

关于日期时间的操作可以分为两种:

  • 转换:与字符串的互相转换,与时间戳的互相转换
  • 计算:计算两个时间点之间的间隔、时间点与时间段的计算(计算下周N、下个月D日、去年M月D日等等)

Java8 提供了三个类:LocalDateLocalTimeLocalDateTime,它们的形式如 2020-01-0112:30:002020-01-01 12:30:00

创建对象

获取类对象的方法非常非常简单

LocalDate now = LocalDate.now();
LocalDate ld = LocalDate.of(2019, 1, 1);
// 获取年月日
now.getYear();
now.getMonthValue();    // 如果你调用了 now.getMonth() ,那么它将返回给你一个大写的英文月份单词
now.getDayOfMonth();
// 顾名应该思义
getDayOfWeek();
getDayOfYear(); 

// 设置年月日
LocalDate ld1 = ld.withYear(2021);      // 2021-01-01
LocalDate ld2 = ld.withMonth(12);       // 2019-12-01
LocalDate ld3 = ld.withDayOfMonth(12);  // 2019-12-12
// 你可能会纳闷,既然是设置,为什么不用单词 set 呢,而用 with
// 因为,set 操作一般是改变调用对象本身,没有返回值;
// 而 with 是在调用对象基础上另外创建一个新对象,设置好值后返回,没有改变调用对象

// 如果你是那个打破砂锅的孩子,你可能会问:为什么不能改变调用对象?
// 因为 LocalDate 是 final 修饰的(final 人称 Java 界的自宫之刀)
// 从物理的角度来讲,目前人类无法改变时间(穿越)

// 如果你有 ld.withMonth(13) 这种反人类历法的操作,当然是会抛出异常的

LocalTimeLocalDateTime 都有类似于 LocalDate 的方法,这里就不一一列举了(因为我感觉自己越来越像 api 文档了)

Java8 API 官方文档直通车

转换

日期时间对象 和 字符串 之间的互相转换:

// LocalDateTime 对象 -> 字符串
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String dateTimeStr = now.format(dtf);
System.out.println(dateTimeStr);

// 字符串 -> LocalDateTime 对象
String str = "2022-01-30 12:15:20";
LocalDateTime dateTime = LocalDateTime.parse(str, dtf);
System.out.println(dateTime);

DateTimeFormatter 类还提供一些现成的 formatter ,比如

DateTimeFormatter.BASIC_ISO_DATE ==> DateTimeFormatter.ofPattern("yyyyMMdd")
DateTimeFormatter.ISO_LOCAL_DATE ==> DateTimeFormatter.ofPattern("yyyy-MM-dd")
// 更多 formatter 可以 api 文档中查询

学习的本质,不在于记住哪些知识,而在于它触发了你的思考。—— 迈克尔·桑德尔

日期时间 和 时间戳 之间的互相转换:

// LocalDateTime 对象 -> 时间戳
LocalDateTime now = LocalDateTime.now();
// 获取系统默认时区
ZoneId systemDefaultZoneId = ZoneId.systemDefault();
Instant instant = now.atZone(systemDefaultZoneId).toInstant();
long timestamp = instant.toEpochMilli();
System.out.println(timestamp);


// 时间戳 -> LocalDateTime 对象
long timestamp2 = 1578919583784L;
Instant instant2 = Instant.ofEpochMilli(timestamp2);
LocalDateTime dateTime2 = LocalDateTime.ofInstant(instant2, systemDefaultZoneId);
System.out.println(dateTime2);

“我不明白为什么要把时间戳搞得这么麻烦!”

另外:java.util.Datejava.time.LocalDateTime 之间的转换需要通过 Instant 实现,它俩都没有提供直接的转换方法

// 获取系统默认时区
ZoneId systemDefaultZoneId = ZoneId.systemDefault();
// Date 转为 LocalDateTime
Date date3 = new Date();
Instant instant3 = date3.toInstant();
LocalDateTime localDateTime3 = LocalDateTime.ofInstant(instant3, systemDefaultZoneId);

// LocalDateTime 转为 Date
Instant instant4 = now.atZone(systemDefaultZoneId).toInstant();
Date date4 = Date.from(instant4);

还有:LocalDateTime 可以由 LocalDateLocalTime 组成,也可以拆分成它俩

LocalDate nowLocalDate = LocalDate.now();
LocalTime nowLocalTime = LocalTime.now();
LocalDateTime nowLocalDateTime = LocalDateTime.of(nowLocalDate, nowLocalTime);

nowLocalDateTime.toLocalDate();
nowLocalDateTime.toLocalTime();

计算

计算时间点与时间点之间的间隔:

// 计算日期时间之间的间隔
LocalTime startTime = LocalTime.now();
LocalTime endTime = startTime.plusHours(1).plusMinutes(50);
Duration duration = Duration.between(startTime, endTime);

// 间隔秒数
duration.getSeconds();
// 间隔天数
duration.toDays();
// 间隔小时数
duration.toHours();
// 间隔分钟数
duration.toMinutes();

Duration.between(start, end) 的参数可以是 LocalDateTimeLocalTime

它只会返回一个整数(舍掉小数后的整数,等同于 floor()),不会返回 1小时50分钟 这样的形式
如果你想要 y年M个月d天 H小时m分钟s秒 这种形式,或许你自己动手组装一下了

// 计算日期之间的间隔
LocalDate startDate = LocalDate.now();
LocalDate endDate = LocalDate.of(2031, 1, 1);
Period pe = Period.between(startDate, endDate);
pe.getYears();
pe.getMonths();
pe.getDays();

时间点与时间段的计算:

    public LocalDateTime plusYears(long years) {
        LocalDate newDate = date.plusYears(years);
        return with(newDate, time);
    }

    public LocalDateTime plusMonths(long months) {
        LocalDate newDate = date.plusMonths(months);
        return with(newDate, time);
    }

    public LocalDateTime plusWeeks(long weeks) {
        LocalDate newDate = date.plusWeeks(weeks);
        return with(newDate, time);
    }

    public LocalDateTime plusDays(long days) {
        LocalDate newDate = date.plusDays(days);
        return with(newDate, time);
    }

    public LocalDateTime plusHours(long hours) {
        return plusWithOverflow(date, hours, 0, 0, 0, 1);
    }

    public LocalDateTime plusMinutes(long minutes) {
        return plusWithOverflow(date, 0, minutes, 0, 0, 1);
    }

    public LocalDateTime plusSeconds(long seconds) {
        return plusWithOverflow(date, 0, 0, seconds, 0, 1);
    }

    public LocalDateTime plusNanos(long nanos) {
        return plusWithOverflow(date, 0, 0, 0, nanos, 1);
    }

    public LocalDateTime minusYears(long years) {
        return (years == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-years));
    }

    public LocalDateTime minusMonths(long months) {
        return (months == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-months));
    }

    public LocalDateTime minusWeeks(long weeks) {
        return (weeks == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeks));
    }

    public LocalDateTime minusDays(long days) {
        return (days == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-days));
    }

    public LocalDateTime minusHours(long hours) {
        return plusWithOverflow(date, hours, 0, 0, 0, -1);
   }

    public LocalDateTime minusMinutes(long minutes) {
        return plusWithOverflow(date, 0, minutes, 0, 0, -1);
    }

    public LocalDateTime minusSeconds(long seconds) {
        return plusWithOverflow(date, 0, 0, seconds, 0, -1);
    }

    public LocalDateTime minusNanos(long nanos) {
        return plusWithOverflow(date, 0, 0, 0, nanos, -1);
    }

看吧,加减年数、月数、天数、小时数、分钟数、秒数、毫秒数都有

想怎么用就怎么用,举个小例子:

LocalDateTime now = LocalDateTime.now();

// 30年后的今天(我还要上班,还没退休)
LocalDateTime after30Years = now.plusYears(30L);
System.out.println(after30Years);

// 347个月后(我就能还清贷款了 ╥﹏╥)
LocalDateTime after348Months = now.plusMonths(347L);
System.out.println(after348Months);

// 11天后(就是除夕了)
LocalDateTime after10Days = now.plusDays(10L);
System.out.println(after10Days);

// 8小时前(我在上班)
LocalDateTime before8Hours = now.minusHours(8L);
System.out.println(before8Hours);

// 3分钟前(我开始听 Let it go 这首歌)
LocalDateTime before3Before = now.minusMinutes(3L);
System.out.println(before3Before);

// 10秒前(写下下面这条代码)
LocalDateTime before10Second = now.minusSeconds(10L);
System.out.println(before10Second);

其实不用区分什么加减的,也可以用 plusXxx 做减法,只要传入负数参数就行了

另外这里还有一类需求,比如:

明年的感恩节是哪天?(每年11月的第四个星期四为感恩节)
下周五是哪天?

这就要用到时间校正器 TemporalAdjuster
这里容我先介绍一下 TemporalAdjuster,它是一个函数式接口,只有一个方法 adjustInto

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}

Temporal 是一个接口,LocalDateTimeLocalDateLocalTime 都是它的实现类

LocalDateTimeLocalDateLocalTime 中都有 with(TemporalAdjuster adjuster) 这个方法用来实现上面提到的另类需求。

// 下周五
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextFriday = now.with(dt -> {
    // dt 是 `Temporal` 对象,但实质上是调用对象的类型
    LocalDateTime dateTime = (LocalDateTime) dt;
    // 非常可惜,没有 withDayOfWeek() 这个方法,要不然就会非常方便了
    int dayOfWeekValue = dateTime.getDayOfWeek().getValue();
    int fridayValue = DayOfWeek.FRIDAY.getValue();
    return dateTime.plusWeeks(1L)
                .plusDays(fridayValue - dayOfWeekValue);
});
System.out.println(nextFriday);

// 明年的感恩节(明年11月第四个星期四)
LocalDate thanksGivingDay = LocalDate.now().with( t -> {
    LocalDate d = (LocalDate) t;
    // 明年11月1日
    LocalDate newDate = d.plusYears(1L).withMonth(11).withDayOfMonth(1);

    int dayOfWeekValue = newDate.getDayOfWeek().getValue();
    int thursdayValue = DayOfWeek.THURSDAY.getValue();
    long plusWeeks = dayOfWeekValue > thursdayValue ? 4L : 3L;
    return newDate.plusWeeks(plusWeeks)
                    .plusDays(thursdayValue - dayOfWeekValue);
});
System.out.println(thanksGivingDay);

其实 TemporalAdjusters 提供了许多 TemporalAdjuster 对象,就像上一节 Stream 中 Collectors 之于 Collector 一样 。

使用 TemporalAdjusters 能够十分方便的实现上面的需求

// 下个周五
LocalDateTime nextFriday = LocalDateTime.now().with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println(nextFriday);

// 明年的感恩节(明年11月第四个星期四)
LocalDate date = LocalDate.now().plusYears(1L).withMonth(11);
LocalDate thanksGivingDay = date.with(TemporalAdjusters.dayOfWeekInMonth(4, DayOfWeek.THURSDAY));
System.out.println(thanksGivingDay);

注意: TemporalAdjusters.next(DayOfWeek day) 方法返回的是 接下来第一个周五,并不是我们一般理解的 下周五,比如说:今天 2020-01-13(周一),那么返回的就是 2020-01-17 四天后的周五。

另外 TemporalAdjusters 并不止提供了上面这2个方法,还有很多其他方法, API 文档 中给出了足够多的例子,一看就明白了。

建议有兴趣的同学,去阅读一些源码,参考 Java8 代码逻辑,然后用其他编程语言实现相同的日期时间操作,因为在其他编程中(比如 javaScript)也会经常用到日期时间的操作

其他特性

罗列出来表示我知道他们,但不表示我理解他们,所以 Let It Go!

接口中的默认方法

接口中的静态方法

Optional 类

Optional<String> op = Optional.of(str);

它是用来标识这个变量有可能为空。

如果一个变量有可能为空,Java8 之前我们每次使用这个变量时,都必须判断它是否为空。现在也是!!

Optional 并不能避免空指针异常,仅仅是表示标识变量可能为空。打个比方:

前面一条路上埋了地雷,但从表面上完全看不出来,除非我们走一步都扔石头试一下,否则说不准哪一步就炸了。而 Optional就是用来标识地雷位置的,我们知道了哪个位置有雷,就会绕着走,从而能够安全通过

另外 Optional 还提供了一个设置默认值的功能,挺好玩的。

Integer pageSize = null;
// 以前我们设置默认值
//pageSize = pageSize == null ? 10 : pageSize;
//System.out.println(pageSize);

// 使用 Optional 设置默认值
pageSize = Optional.ofNullable(pageSize)
    .orElse(20);
System.out.println(pageSize);

// 自定义默认值
Integer defaultPageSize = Optional.ofNullable(pageSize)
        .orElseGet(() -> {
            return new Integer(50);
        });

结语

Java8 新特性系列随便到此就结束了。

最最关键的,还是多看 Java 官方 API 文档!!

我在想可能我们被矫枉过正了。各种各样技术群最多的回答都是:去问百度!! 百度全知道吗?!

说实在的,在今天这个时代,是个人都能在网络上发表文章言论,然后大家再互相转载,假的都能成为真的!
没有经过验证就转载的;在当时有效,现在过时了的;随便在文章中一个转载链接的 ... 比比皆是

这里有一个开眼的短视频,介绍 虚假新闻是如何在传播

最恼人的是,百度搜索一个关键词 XXX,点进去一篇博客文章,结果文章内容根本就没有这个关键词,那么关键词到底在哪呢?
关键词在网站的推荐文章列表标题里,所以百度爬取到了,显示在搜索结果里了。真是日了狗了!!

百度搜到的博客能别看就别看,看也要看那些经常更新,比较靠谱的。

靠谱的学习途径:

  1. 官方最新 API 文档
  2. 书籍/视频(要注意权威性和时效性)
  3. 经常更新靠谱的博客

轻易不要百度!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • Java8新特性的功能已经更新了不少篇幅了,今天重点讲解时间日期库中DateTime相关处理。同样的,如果你现在依...
    程序新视界阅读 338评论 0 1
  • 前言:Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geek...
    糖宝_阅读 1,321评论 1 1
  • 1、LocalDate、LocalTime、Instant、Duration、Period 1)使用LocalDa...
    夏与清风阅读 550评论 0 0
  • 清明的整个假期陪了宝宝。 清明那天祭拜父亲,第二题天回老家本意是看梨花,梨花败了,改看了桃花。今儿去了一二九师缅怀...
    简小单_阅读 256评论 0 2
  • 学渣顾名思义就是差等生。这类人在处于这个标签之下,不同的人有不同的做法。一小部分人可能会拼了命地摆脱这个阴影,而往...
    逸轩君阅读 482评论 0 3