在 Java 8 之前,对于日期和时间的处理是能过 Date 和 Calendar 来完成的,因为长时间没接触 Java 了,我对日期的处理也还停留在它们上,最近重新学 Java 才知道,Java 8 新推出了一套日期处理的API,在这就来探讨一下它们跟之前的日期处理类有什么不同,和新的 API 有什么优点,怎么使用。

本文将以下顺序去展开:

  1. 为什么要推出新的日期处理 API,过去的日期处理存在哪些问题?
  2. Java 8 的日期 API 做了哪些优化,有什么新功能?
  3. Java 8 的日期 API 的使用。

Date 存在的问题

先来看看 Date 存在一些什么问题,我在网上查了一些资料,都是说 Date 存在线程安全和易用性上的问题。先来看看具体是怎样的问题。

线程安全问题

写段程序在多线程下跑一下日期格式化

public static void main(String[] args) {        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");        for(int i=0; i < 5; i++) {            new Thread(() -> {                for(;;) {                    try{                        System.out.println(Thread.currentThread() + ":" + simpleDateFormat.format(new Date(Math.abs(new Random().nextLong()))));                    } catch (Exception e) {                        e.printStackTrace();                        System.exit(1);                    }                }            }).start();        }    }

可以看到,程序运行中途报错了

我们点进SimpleDateFormat的源码看看,SimpleDateFormat继承于DateFormat,而DateFormat内部保存着一个全局的calendar对象的,而对日期的格式化或者解析都是要操作这个对象。然后我们再来看看format这个方法的源码。

可以看到在第943行,对calendar赋值了需要格式化的date对象,如果在多线程环境下,线程1设置了calendar的时间,但是还没完成格式化的逻辑,这里线程2又对calendar设置时间的时候,覆盖了线程1的设置,那么线程1在后面读取calendar对象的时间就会导致报错。为了解决这个问题,只能给每个线程都创建一个SimpleDateFormat,跟其它线程隔离起来。

同理,对于parse方法,每一次把日期字符串解析成对象时,calendar都会把上一次的保存的日期时间信息全部清空,然后保存最新的日期时间信息,在多线程下就会出来,线程1设置了日期时间后,线程2又把它给清空了,最后程序就会报错了。

parse方法最后会调用下面的方法,在第114行对日期信息进行了清空。

易用性

相信 Date 和 Calendar 的难用大家都清楚。就随便举例几点。

Date的月份开始是从0开始,以11结束的,每次操作月份都要做加1操作跟现在月份匹配上。

想要对日期进行运算,加一天,减一天,加一个月,减一个月等操作,只能通过Calendar来进行,DateCalendar转换来转换去相当的麻烦。

Date把日期、时间揉合在了一起,当面临只需要处理日期部分或者时间部分的场景,Date就显得有点臃肿了,而且Date的输出可读性也不好,不对它进行格式化的话看起来很难受。

Java 8 新日期 API

基于上述问题,在 Java 8 对日期进行了优化,首先是将日期和时间设计为不可变类型,就像String类型一样,这样就避免了多线程下对日期的修改导致的线程安全问题,每次对日期的操作都会生成一个新的日期对象;另外还把功能进一步细化了,对日期的运算更加便利,输出也更加人性化。

下面来看看 Java 8 主要提供了哪一些常用的日期 API。

从上图可以看到,Java 8 把日期和时间拆分出来了,LocalDateTime包含日期和时间,LocalDate只包含日期部分,LocalTime只包含时间部分,Instant代表时间线上的一个瞬时时间点,但默认时区是UTC+0的。

public static void main(String[] args) {        System.out.println("LocalDateTime: " + LocalDateTime.now());        System.out.println("LocalDate: " + LocalDate.now());        System.out.println("LocalTime: " + LocalTime.now());        System.out.println("Instant: (UTC+0)" + Instant.now());        System.out.println("Instant: (UTC+8)" + Instant.now().atZone(ZoneId.systemDefault()));    }

LocalDateTimeLocalDateLocalTimeInstant都实现了TemporalTemporalAdjuster接口,Temporal提供了一些对日期运算的接口,如对日期的加减,TemporalAdjuster提供只提供了一个接口,用于对日期/时间对象的调整。

另外LocalDateTime内部只是封装了LocalDateLocalTime,当对LocalDateTime进行操作时,都是针对指定的日期或者时间部分去进行操作的。

另外LocalDateTimeLocalDateLocalTimeInstant在运算中都有各自约束的范围,LocalDateTime可以支持日期和时间的运算;LocalDate只支持最小粒度为1天的运算,不能LocalDate上对时间进行运算;LocalTime支持纳秒到小时的运算,不能对日期进行运算;Instant只支持纳秒到秒的运算。如果进行超出规定范围内的运算就抛出不支持的异常。

但是LocalTimeInstantplus有些特殊,支持到天的运算,是因为plus在内部把天转换为在范围内的单位再进行运算。

Java 8 新日期 API 的使用

上面讲到了 Java 8 对日期 API 在安全性和易用性上的优化。现在来总结一下日常常用 API 具体怎么用。

这里以LocalDate为例,LocalDateTimeLocalTime的用法差不多。

public static void main(String[] args) {        // 获取当天日期时间        LocalDate today = LocalDate.now();        print("获取当天日期时间: ", today);        // 加一天        LocalDate tomorrow = today.plusDays(1);        print("加一天: ", tomorrow);        // 加一个月        LocalDate nextMonth = today.plusMonths(1);        print("加一个月: ", nextMonth);        // 减一天        LocalDate yesterday = today.minusDays(1);        print("减一天: ", yesterday);        // 减一个月        LocalDate lastMonth = today.minusMonths(1);        print("减一个月: ", lastMonth);        // 获取今天是本月第几天        int dayOfMonth = today.getDayOfMonth();        print("获取今天是本月第几天: ", dayOfMonth);        // 获取今天是本周第几天        int dayOfWeek = today.getDayOfWeek().getValue();        print("获取今天是本周第几天: ", dayOfWeek);        // 获取今天是本年第几天        int dayOfYear = today.getDayOfYear();        print("获取今天是本年第几天: ", dayOfYear);        // 获取本月天数。        int daysOfMonth = today.lengthOfMonth();        print("获取本月天数: ", daysOfMonth);        // 获取本年天数        int daysOfYear = today.lengthOfYear();        print("获取本年天数: ", daysOfYear);        // 获取本月指定的第n天        LocalDate date1 = today.withDayOfMonth(15);        print("获取本月指定的第n天: ", date1);        // 获取本月的最后一天        LocalDate lastDaysOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());        print("获取本月的最后一天: ", lastDaysOfMonth);        // 日期字符串解析。 严格按照ISO yyyy-MM-dd 验证        LocalDate date = LocalDate.parse("2021-01-17");        print("日期字符串解析: ", date);        // 日期字符串解析。 自定义格式        DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-M-dd");        LocalDate date2 = LocalDate.parse("2021-1-17", dft);        print("日期字符串解析(日期字符串解析): ", date2);        // 格式化日期        String dateStr = today.format(dft);        print("格式化日期: ", dateStr);        // 自定义日期        LocalDate cusDate = LocalDate.of(2020, 8, 14);        print("自定义日期: ", cusDate);        // 日期比较        boolean before = today.isBefore(tomorrow);        print("今天是否比明天早: ", before);        boolean before1 = today.isBefore(yesterday);        print("今天是否比昨天早: ", before1);        boolean after = today.isAfter(tomorrow);        print("今天是否比明天晚: ", after);        boolean after1 = today.isAfter(yesterday);        print("今天是否比昨天晚: ", after1);        // 获取两个时间相差多少天/周/月...  根据单位不同返回不同        long until = today.until(nextMonth, ChronoUnit.WEEKS);        print("今天到下个月相差几周: ", until);        Month month = today.getMonth();        print("月份:", month);        print("月份: ", month.getValue());    }

从最后月份的输出可以看出,Java 8 把月份优化成了一个枚举类,也把月份区间调整为1~12了。

Duration 和 Period

Java 8 还提供了 2 个计算两个时间/日期差的 API。

Duration里面封装了secondsnanos,前者是秒,后者是纳秒,代表着两个时间间的差值;

Period里面封装了daymonthyears 3 个属性,代表的是两个日期间的差值。

所以,Duration只能计算包含有时间的对象,比如LocalDateTimeLocalTimeInstant,如果计算LocalDate的话会不支持的异常。

同理,Period也就只支持LocalDate的计算。

下面来看看它们有哪些用法。

Duration
public static void main(String[] args) {        LocalDateTime today = LocalDateTime.now();        LocalDateTime tomorrow = today.plusDays(1);        // 根据两个时间获取 Duration        Duration duration = Duration.between(today, tomorrow);        print("获取纳秒数差值:", duration.toNanos());        print("获取毫秒数差值:", duration.toMillis());        print("获取秒数差值: ", duration.getSeconds());        print("获取分钟数差值:", duration.toMinutes());        print("获取小时数差值:", duration.toHours());        print("获取天数差值:", duration.toDays());        // 当第1个时间比第2个时间小时为false, 反之true。可以用来判断2个时间的大小。        boolean negative = duration.isNegative();        print("isNegative: ", negative);        // 以1天的差值创建Duration        Duration duration1 = Duration.ofDays(1);        print("以1天的差值创建Duration: ", duration1.getSeconds());    }

Duration也支持plusminus操作,这里就不演示了。

另外Duration还有个功能,可以通过解析字符串来生成对象。字符串的规则是这样:PnDTnHnMn.nSP为固定开头,n为数字,D为天数,T代表后面是时间部分,HMS分别时、分、秒。字母大小写不敏感,可大写可小写。另外还支持+-+为往上加时间,-为往下减时间。

public static void main(String[] args) {        Duration duration = Duration.parse("P1DT1H1M1S");        print("当前时间加上1天1小时1分钟1秒的差值: ", duration.getSeconds());        Duration duration1 = Duration.parse("P2D");        print("当前时间加上2天的差值: ", duration1.getSeconds());        Duration duration2 = Duration.parse("PT2H");        print("当前时间加上2小时的差值: ", duration2.getSeconds());        Duration duration3 = Duration.parse("PT-2H");        print("当前时间减去2小时的差值: ", duration3.getSeconds());        Duration duration4 = Duration.parse("PT-2H30M");        print("当前时间减去1小30分的差值: ", duration4.getSeconds());        Duration duration5 = Duration.parse("PT-2H-30M");        print("当前时间减去2小30分的差值: ", duration5.getSeconds());        // 上面的也可以写成这样        Duration duration6 = Duration.parse("-PT2H30M");        print("当前时间减去2小30分的差值: ", duration6.getSeconds());    }

每个n前面都是隐式的添加了一个+,像-2H30M这种意思就是减去2个小时再加30分钟,那就是1个小时30分钟咯;但是如果在P前面加一个-的话,会对里面所有的数字都会产生影响。其实就是像小学数学一样,用括号把PnDTnHnMn.nS括起来,(PnDTnHnMn.nS),在最外面加一个-,那里面的符号就全部取反了,但是单独在某个数字前加的话只会影响到它自己。

Period

PeriodDuration其实用法差不多,都是表达时间上的差值。只不过一个是表达日期,一个是表达时间,粒度不同。就不演示了。

总结

Java 8 的日期处理 API 常用的功能方法总结得差不多了,基本上日常使用的也就大概这么多了,这一套日期 API 搞清楚它们的区别和背后逻辑后用起来很方便,本文没有讲到的用法,开发使用的时候查一下文档也可以马上用起来了,底层都是这些基础的知识点。

©著作权归作者所有:来自51CTO博客作者wx5a2f2ffe04804的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 老艿艿说:关于时间管理的分享
  2. Windows 服务器安装远程桌面及破解120天时间限制授权
  3. 理解算法的时间复杂度[每日前端夜话0x82]
  4. PMP备考复盘,可供学习时间有限的同学参考
  5. AngularJS 日期时间选择组件(附详细使用方法)
  6. GMT UTC CST ISO 夏令时 时间戳,都是些什么鬼?
  7. 美团面试官:生成订单后一段时间不支付订单会自动关闭的功能该如何
  8. 2020 年,我的年终总结!时间与人生(下)
  9. 半年北漂生活,时间与人生(上)

随机推荐

  1. 【Android】基于XMAPP协议实现Android推
  2. Eclipse环境下格式化Android的代码风格
  3. Android(安卓)方向传感器 (Orientation Se
  4. View事件传递分析
  5. 深入理解Activity启动流程(一)–Activity
  6. Android应用使用自定义字体
  7. Android - Android Studio 的 Preview窗
  8. Android中visibility的3个属性说明
  9. Android 中文api (81)——InputMethod [
  10. Android MimeType和MimeTypeMap的介绍