前言
相信大家已经很熟悉Date
, SimpleDateFormat
和Calendar
类了,这是以前我们常用来在java中处理时间和日期的类了,但是它们存在一些问题,比如线程不安全,设计不直观以及其他问题。
以前处理时间和日期的API分散在不同的包下面,比如Date
和Calendar
类在java.util
包下,但是SimpleDateFormat
在java.text
包下面。
为了解决这些问题,JDK8推出了新的时间和日期API,新的时间日期API都在java.time
包下,我们这里主要介绍LocalDate
, LocalTime
, LocalDateTime
, DateTimeFormatter
这几个类,因为我们日常处理时间和日期使用这几个类就已经足够,其他的类仅做简单的介绍。
一、新的API新在哪里
之前我们已经简单提到了它们所在的包不一样,但是这肯定不是他们最主要的区别,这只是表面现象罢了。
旧版的Date
对象是可以改变的,我们可以调用它的setXxx
方法来改变它,这样就有一个问题,在多线程运行的情况下,需要程序员自己做好同步处理,不然很有可能出现难以预测的并发问题。
并且旧版的日期API需要配合SimpleDateFormat
类来使用,整个体系不直观。
新版时间日期API的对象是不可变的,意味着一旦创建就不能修改。如果我们需要在当前时间对象上变更指定的时间单位(例如增加一天),那么它的API会返回一个新的对象(像不像String),这样的话只有读取没有修改,自然就是线程安全的了。
新版时间日期API针对不同的需求有三种类,分别是:LocalDate
,LocalTime
,LocalDateTime
这三种类的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的使用产生疑惑的时候,不妨看看它的源码,我们学习的方式要多样且灵活,如果有疑惑,可以在源码中找到答案,比如我看了LocatDate
的parse
方法的源码实现,发现了它默认的时间格式叫什么名字,能够匹配哪些时间格式。
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
,同时会查看一些源码,提高一下学习的深度。
不知道大家有没有一个疑惑,之前讲LcoalDate
和LocalTime
的时候,里面有个函数叫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
,它不是只能解析一种格式,它通过builder
的optionalStart
方法实现了支持多种格式,更适合实际的业务场景。
总结
JKD8新版的时间日期API还有其他新的功能,这里主要介绍了开发中常用的几个类,分别是处理日期的LocalDate
,处理时间的LocalTime
,处理日期时间的LocalDateTime
,以及用于解析时间字符串的DateTimeFormatter
。
我们对于技术的学习要循序渐进,先把这几个类掌握好,然后再去学习java.time
包下面的其他类就是水到渠成的事。