JDK8新特性系列——时间日期API


前言

相信大家已经很熟悉Date, SimpleDateFormatCalendar类了,这是以前我们常用来在java中处理时间和日期的类了,但是它们存在一些问题,比如线程不安全,设计不直观以及其他问题。

以前处理时间和日期的API分散在不同的包下面,比如DateCalendar类在java.util包下,但是SimpleDateFormatjava.text包下面。

为了解决这些问题,JDK8推出了新的时间和日期API,新的时间日期API都在java.time包下,我们这里主要介绍LocalDate, LocalTime, LocalDateTime, DateTimeFormatter这几个类,因为我们日常处理时间和日期使用这几个类就已经足够,其他的类仅做简单的介绍。


一、新的API新在哪里

之前我们已经简单提到了它们所在的包不一样,但是这肯定不是他们最主要的区别,这只是表面现象罢了。

旧版的Date对象是可以改变的,我们可以调用它的setXxx方法来改变它,这样就有一个问题,在多线程运行的情况下,需要程序员自己做好同步处理,不然很有可能出现难以预测的并发问题。
并且旧版的日期API需要配合SimpleDateFormat类来使用,整个体系不直观。

新版时间日期API的对象是不可变的,意味着一旦创建就不能修改。如果我们需要在当前时间对象上变更指定的时间单位(例如增加一天),那么它的API会返回一个新的对象(像不像String),这样的话只有读取没有修改,自然就是线程安全的了。
新版时间日期API针对不同的需求有三种类,分别是:LocalDateLocalTimeLocalDateTime
这三种类的API风格统一,易于学习并掌握,学习成本更低

二、示例代码

1. LocalDate类

LocalDate是用于表示和处理日期的类,不处理时间,适合一些对时间不敏感,只处理日期的业务场景。
我们来看看它有哪些常用的函数

    @Test
    public void demo1(){
        // 获取当前日期
        LocalDate now = LocalDate.now();

        // 根据传入的参数构造日期对象 of方法被重载过,展示它的两种用法
        LocalDate specifyDate = LocalDate.of(2023,1, 1);
        // 下面两种调用的是LocalDate的同一个of方法,但是是调用Month的of方法获取Month的枚举
        LocalDate specifyDate1 = LocalDate.of(2023, Month.JANUARY, 1);
        LocalDate specifyDate2 = LocalDate.of(2023, Month.of(1), 1);

        // 获取日期的各个部分
        int year = specifyDate.getYear();
        int month = specifyDate.getMonthValue();
        Month monthEnum = specifyDate.getMonth();
        // 获取周几(1-7)
        int dayOfWeek = specifyDate.getDayOfWeek().getValue();
        // 获取多少号(1-31)
        int dayOfMonth = specifyDate.getDayOfMonth();
        // 获取是一年的第多少天(1-366)
        int dayOfYear = specifyDate.getDayOfYear();

        // 修改时间,其实是基于当前对象的属性构建新的对象
        LocalDate newDate = specifyDate.plusYears(-1);
        System.out.println("减去1年后的年份是:"+newDate.getYear());


        // 把字符转换成日期对象,parse只有一个参数的话,使用默认的日期格式:DateTimeFormatter.ISO_LOCAL_DATE
        // 用短横线分割,月和天必须是两位数
        LocalDate parsedDate = LocalDate.parse("2023-01-01");
    }

我们在对一些API的使用产生疑惑的时候,不妨看看它的源码,我们学习的方式要多样且灵活,如果有疑惑,可以在源码中找到答案,比如我看了LocatDateparse方法的源码实现,发现了它默认的时间格式叫什么名字,能够匹配哪些时间格式。

2. LocalTime

了解了LocalDate类的使用,我们再来学习LocalTime
LocalTime是用于表示和处理时间的类,并且不止支持时、分、秒,还支持纳秒

接下来我们看看代码示例

    @Test
    public void demo3(){
        // 获取当前时间
        LocalTime now  = LocalTime.now();

        // 使用参数构造时间对象
        LocalTime specifyTime = LocalTime.of(21, 30);
        // 重载形式1
        LocalTime specifyTime1 = LocalTime.of(21, 29, 59);
        // 重载形式2
        LocalTime specifyTime2 = LocalTime.of(20,28,55,123456789);

        // 获取时间的各个部分
        int hour = specifyTime2.getHour();         // 20
        int minute = specifyTime2.getMinute();     // 28
        int second = specifyTime2.getSecond();     // 55
        int nano = specifyTime2.getNano();         // 123456789

        // 修改时间 由于时间对象是不可变的,所以会返回新的对象
        // 而不是在原来的对象上面做修改
        LocalTime newTime = specifyTime.plusHours(2);   // 23:30
        LocalTime anotherTime = specifyTime.withHour(5); // 05:30

        // 比较时间 比较一个时间是否大于或者小于另一个时间
        boolean isBefore = specifyTime.isBefore(specifyTime1);
        boolean isAfter = specifyTime.isAfter(specifyTime1);

        // 采用默认的格式把字符串转换成时间
        LocalTime parsedTime = LocalTime.parse("12:30:45.123456789");
        System.out.println(parsedTime.getHour());
    }

看到这里了,有什么感想,是不是感觉API风格很统一,LocalDateTime的API我还没讲,但是你是不是已经猜到了有哪些API了,如果没有这个想法,建议你回去从LocalDate开始重新看 。

3. DateTimeFormatter

这里我就不讲LocalDateTime了,直接用,我们讲一个很常用但是没怎么重视的类DateTimeFormatter以及它的builder:DateTimeFormatterBuilder,同时会查看一些源码,提高一下学习的深度。

不知道大家有没有一个疑惑,之前讲LcoalDateLocalTime的时候,里面有个函数叫parse,我都只用了默认的格式来转换字符串,言外之意就是可以自定义格式,还有就是我怎么知道默认格式长啥样的?

在旧版API中,我们使用的是SimpleDateFormat类来定义时间格式,然后调用它的方法来转换成Date对象,那么这里我们该怎么自定义格式,像以前那样写行不行?

这里我们写几行代码来看看怎么用

    @Test
    public void demo3(){
        // 获取当前日期和时间
        LocalDateTime now = LocalDateTime.now();
        // 熟悉的写法它来啦,虽然不一样,但是它们很像
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println(now.format(formatter));

        final String dateTimeStr = "2023-01-01 12:01:35";
        // 这里就传入了自定义的formatter来转换字符串为LocalDateTime对象
        LocalDateTime specifyDateTime = LocalDateTime.parse(dateTimeStr,formatter);
        System.out.println(specifyDateTime.getYear());
    }

在这段代码里面,构造自定义formatter的写法和SimpleDateFormat差不多,在细微的语法上有点区别。那么还有没有其他方式来构造DateTimeFormatter呢,答案是:有!

首先ofPattern还有另一个重载形式ofPattern(String pattern, Locale locale),能传入Local对象,Local表示地方,能够输出具有地域特色的时间格式,比如美国输出英文的星期,中国输出中文的星期,很好理解吧。

除了使用DateTimeFormatter自己的函数以外,我们还可以使用DateTimeFormatterBuilder,看类名就知道这是采用 建造者模式 来设计的一个类,从这我们就能看出来,DateTimeFormatter是一个比较复杂的类,不然也不会采用建造者模式来构建它的实例对象。

我之前调用的LocalTime.parse,是采用默认的格式来转换字符串,我们是不是很容易就能想到,它是在自己内部调用了另一个重载的parse(CharSequence,DateTimeFormatter)方法,然后固定传入一个DateTimeFormatter对象,我们查看源码看看是不是这样。

在这里插入图片描述
嘿,还真是这样,并且重点在parse的第二个入参上面,好像是类常量,意思是DateTimeFormatter自带了很多静态常量,分别代表一些常用时间格式的预设,我们点进去看看长什么样。

在这里插入图片描述
首先定义了一个静态常量,然后在 静态代码块 里面对静态常量初始化,使用DateTimeFormatterBuilder来构建,我们这里来简单解析一下这个构建过程,以后的话就能自己通过阅读代码来理解代码库作者的意图了。

appendValue 方法添加附带规则的值到格式化器中,并指定最小宽度
appendLiteral 方法添加固定的字符串到格式化器中,比如我们用来添加分隔符:
optionalStart 的意思就是从这里开始是可选项,后面的不是必须的
appendFraction 是添加小数的解析,这里的纳秒是秒的小数,所以用这个方法来添加纳秒解析

了解这些方法之后我们看看这个格式实际上是怎样的
首先添加了最小宽度为2的小时数解析,然后使用冒号作为分隔符
接下来添加了最小宽度的为2的分钟数解析,这后面紧跟optionalStart表示后面的解析是可选的
接下来添加了冒号:作为分隔符
下一步继续添加对于秒钟的解析,最小宽度为2
下一步添加了对于纳秒的解析,添加解析规则,规定了最小和最大宽度以及是否有小数点

所以我们分析下来,ISO_LOCAL_TIME可以解析下面这些字符串
12:30
12:30:55
12:30:55.123456789

看了下来,可能有一个疑惑,ofPattern挺好用的,直接传入占位符就能解析时间日期了,使用builder的方法来构造DateTimeFormatter好复杂呀。
这里需要知道的是,之所以复杂,是因为它和ofPattern相比更灵活,就像我刚才解析的DateTimeFormatter.ISO_LOCAL_TIME,它不是只能解析一种格式,它通过builderoptionalStart方法实现了支持多种格式,更适合实际的业务场景。


总结

JKD8新版的时间日期API还有其他新的功能,这里主要介绍了开发中常用的几个类,分别是处理日期的LocalDate,处理时间的LocalTime,处理日期时间的LocalDateTime,以及用于解析时间字符串的DateTimeFormatter
我们对于技术的学习要循序渐进,先把这几个类掌握好,然后再去学习java.time包下面的其他类就是水到渠成的事。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值