前言
Java8,发布于2014年,如今已是2021年,Java16都已发布,但Java8依然是永久的神。在笔者看来,随着掌握SpringBoot逐渐成为JavaWeb开发者的必备技能,Java8得以真正的在企业中普及起来。以笔者自身经历来说,自去年七月份参加工作以来,新开展的项目环境默认都是JDK8了,技术的升级革新总是让人兴奋不已。
但是,从笔者的观察中,发现Java8的新特性在日常开发中,很多小伙伴并没有尝试着去使用,体会新特性的优势。虽然自古至今技术的演进并不一定都是朝着正确的方向发展,但我们依然要充满期待,勇敢的去尝试。
Java8的新特性包括:Lambda表达式、方法引用、默认方法、Stream流、新的日期时间API、Optional类等。本篇博文不是一篇教程,也不会对所有的新特性都进行讨论,仅仅是因为又温习了一遍新的日期时间API,针对新的日期时间API有一些感想,敢竭鄙怀,恭疏短引。
Java8之前的日期时间API及存在的陷阱和坑
Java8之前,处理日期时间最常用的有四大金刚:Date、Calendar、GregorianCalendar、SimpleDateFormat。这四大金刚真的是被诟病了很久很久,久到那时候我还没接触这个行业,连数据库三个字都没听说过的小白。每每想到这,我都非常感谢我的研究生导师景老师,领我进入这个只有0和1的世界。
旧API存在的问题:
- 线程不安全。上述四大类都不是线程安全的,对于开发者额外的处理并发问题,增加工作量,尤其是互联网时代,并发的场景比比皆是,放任不管随时可能导致线上事故。
- 设计不佳。如果足够细心的话,可以发现这个四个类并不在同一个包下,日期类在java.util包下,而格式化类在java.text包下,此外,Date没有可以直接操作日期时间的方法,怎么说都非常的不方便。
- 时区处理困难。因为设计不佳,开发人员不得不编写大量的代码处理时区问题,无疑有增加了不小的工作量,还容易出错。
- 其他一些问题。还有一些坑估计是每个人都经历过的,比如,month是从0开始的,而day又是从1开始的。
福音来了:Java8新的日期时间API
技术的革新总是为了解决旧问题的,这一点始终如此。当然,技术的革新又会产生新的问题,世界也是在矛盾中不断发展的,这又是哲学范畴的问题了。
新的日期时间API统一放在java.time包及其子包下,见包知其义。
新的API解决是什么问题?(有哪些优点)
- 线程绝对安全。消除了setter方法,同时对实例的任何更改都会返回一个新实例,这一点非常重要。
- 提供非常多的方法用来操作日期和时间,并且返回的都是全新的实例。
- 借鉴了优秀的第三方日期时间库joda。
- 对于时区而言,引入了域domain的概念。
有哪些新的类?
- LocalDateTime
- LocalDate
- LocalTime
- Duration
- Period
- Instant
- TemporalAdjuster
- Clock
- ZonedDateTime
- ZonedDate
- ZonedTime
- ZoneId
- DateTimeFormatter
- ……
对于API的具体使用,不是本篇的重点,在文章的最后我会附上一篇比较用心的博文链接,对于常用API的使用都做了讲解与演示。
本篇博文只是讨论几个比较有意思的地方
为什么说LocalDate、LocalTime、LocalDateTime不承载时区信息?
其实这个问题,看一下源码就非常的清晰,让我们一起来看一下LocalDateTime类的静态方法now()
/**
* Obtains the current date-time from the system clock in the default time-zone.
* <p>
* This will query the {@link Clock#systemDefaultZone() system clock} in the default
* time-zone to obtain the current date-time.
* <p>
* Using this method will prevent the ability to use an alternate clock for testing
* because the clock is hard-coded.
*
* @return the current date-time using the system clock and default time-zone, not null
*/
public static LocalDateTime now() {
return now(Clock.systemDefaultZone());
}
从源码可以很明显的看到,LocalXxx类的日期时间类的now()方法,参数是Clock.systemDefaultZone(),默认实例化的就是当前时区的时钟时刻,因此不再承载时区信息。
LocalDateTime、LocalDate如何与Date互相转换?
其实这个问题才是我写这篇博文最想探讨的问题点。当最开始学习新的日期时间API时,首先映入眼帘的一定是LocalDateTime的使用,会发现,真的非常的方便好用,随着学习的深入,尤其是到了Instant时候,会非常的不适应,Instant也不承载时区信息,那会产生一个问题:前面说过LocalDateTime也不承载时区信息,为什么我打印出来的时间相差8小时?
请不要忘记,之所以称为Local,是携带了本地的时区信息,只是无法更改而已,而我们就在东八区,因此相差8小时。
所以,在很多的博文中,大家会发现一句话,LocalDateTime无法与Instant直接相互转换。Instant与Date都表示Unix时间戳,很轻易的可以相互转换。
让我们回到最开始的问题,既然LocalDateTime可以与Instant相互转换(不能直接转换不代表不能转换),而Instant与Date可以相互转换,那基本思路就理清了。
因此,虽然转换的方式有好几种,但是万变不离其宗。
下面上实例
1. Date与Instant相互转换
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import java.time.Instant;
import java.util.Date;
/**
* @version V1.0
* @Title:
* @Package PACKAGE_NAME
* @Description: TODO
* @author: zongshaofeng
* @date 2021/4/28
*/
@RunWith(SpringRunner.class)
public class TestTimeDate {
@Test
public void test1() {
System.out.println("============Date与Instant互相转换============");
Instant instant = Instant.now();
System.out.println("打印Instant对象" + instant);
Date date = Date.from(instant);
System.out.println("Date.from()将Instant转为Date,打印date" + date);
System.out.println();
Date date1 = new Date();
System.out.println("打印Date对象" + date1);
Instant instant1 = date1.toInstant();
System.out.println("date1.toInstant()将Date转为Instant,打印instant1" + instant1);
System.out.println("============================================");
}
}
打印结果
我相信,很多人看到这个打印结果,要准备喷我了,其实我在学习的时候也很纳闷,尤其是突然感觉前面学习的所有东西瞬间全乱套了,结果并不相等啊,其实看一下Date的源码就很自然的明白了。Date在打印对象的时候,其实是加上了默认当地的时区信息,所以你就看到了相差了8小时时间。让我们一起看一下源码:为了凸显相关源码,去掉了注释
//存储的Date毫秒数
private transient long fastTime;
//默认构造函数
public Date() {
this(System.currentTimeMillis());
}
//有参构造函数
public Date(long date) {
fastTime = date;
}
//toString方法
public String toString() {
// "EEE MMM dd HH:mm:ss zzz yyyy";
BaseCalendar.Date date = normalize();
StringBuilder sb = new StringBuilder(28);
.................
}
//关键之处:normalize方法
private final BaseCalendar.Date normalize() {
if (cdate == null) {
BaseCalendar cal = getCalendarSystem(fastTime);
cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime,
TimeZone.getDefaultRef());
return cdate;
}
}
不知道看了上面的源码,小伙伴发现了没有,toString()在一开始就调用了normalize()方法,而normalize方法中使用的是fastTime(即Unix时间戳)和TimeZone.getDefaultRef()作为参数,明白了吗?这不就是加入了当地时区信息了嘛。
所以说,万事基于源码,笔者真的很建议大家多问几个为什么,多去源码找答案。
2. LocalDateTime与Date相互转换
@Test
public void test1() {
System.out.println("============Date与LocalDateTime互相转换============");
Date date = new Date();
System.out.println("打印Date对象" + date);
LocalDateTime localDateTime1 = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("将Date转为LocalDateTime,方法一,打印localDateTime1" + localDateTime1);
LocalDateTime localDateTime2 = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("将Date转为LocalDateTime,方法二,打印localDateTime2" + localDateTime2);
LocalDateTime localDateTime3 = date.toInstant().atOffset(ZoneOffset.ofHours(8)).toLocalDateTime();
System.out.println("将Date转为LocalDateTime,方法三,打印localDateTime3" + localDateTime3);
System.out.println();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("打印LocalDateTime对象" + localDateTime);
Date date1 = Date.from(localDateTime.toInstant(ZoneOffset.ofHours(8)));
System.out.println("将LocalDateTime转为Date,方法一,打印date1" + date1);
Date date2 = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("将LocalDateTime转为Date,方法二,打印date2" + date2);
System.out.println("============================================");
}
打印结果
其实转换的方法很多,但是万变不离其宗,都是通过与Instant或ZonedDateTime建立联系,他们之间的关系说白了很简单,不要被这么多的新API所吓住,其实扒了皮就很明白:Date与Instant可以互相转换、Instant与ZonedDateTime可以互相转换、ZonedDateTime与LocalDateTime可以互相直接转换,掌握了这层关系,不管哪一种实现方式,都非常的容易理解。
再次声明,本文不是一篇教程,不会阐述如何调用API,在本文最后会附上笔者在学习Java8新的日期时间API时参考的一篇非常详细的博文,也是本文的参考文档。本文更多的是传达笔者对于在学习过程中遇到的一些比较有意思的点与大家共同分享。
还是发自内心的建议大家认真学习一下新的日期时间API,在日常开发工作中大胆的尝试使用,这样才能熟练运用。
下一次,让我们来聊一聊,Java8的另一个新特性:Stream流。
希望笔者的分享能够帮助到每一个在学习路上的小伙伴,一起加油。
最后,感谢ThinkWon博主的文章,让我得以完成本篇博文。
学习API请看:
ThinkWon博主文章地址:Java8日期时间API