java.util.Date不香吗?我为什么要用新API?需求都写完了吗?


旧API能用,但真的不香

提到Java的日期,很多人第一时间就是java.util.Datejava.utils.Calendar。毕竟这俩是Java从远古时期以来就一直在用的时间处理的api。放到现在来看,这套古老的时间处理方案呢就稍显"老态龙钟"。

还有用来格式化时间的java.text.SimpleDateFormat。这个类呢,不是线程安全的

Date呢,是@since JDK 1.0的。就是一个精度为毫秒的时间瞬间Calendar呢,是@since JDK 1.1的。用来提供某个时间瞬间的年、月、日、等跟日历有关的字段各种转换的方法。 SimpleDateFormat呢,我没找到是哪本版本来的……。继承了DateFormat。用来格式化序列化和反序列化Date


旧API的缺陷

Calendar各种域字段设计不统一,晦涩难懂

先问大家一个问题。

我国今年的母亲节刚过不久。我国每年的母亲节五月的第2个周日。 写一个main方法,用Date, Calendar, SimpleDateFormat三个类。以格式"yyyy-MM-dd",输出今年母亲节的日期。


需要一个具体的时间点时,Calendar效率低下,且容易出错

用Calendar来操作时间,效率实在是太低了。 比如要一个精准的时间点:昨天的07:59:43.779。就要set一堆的东西,还有可能会漏了一个自己都不知道。


可能上述例子有点极端,大部分情况并不会遇上。 不过实际情况总是,产品的想法总是比你实现的方法多


SimpleDateFormat不是线程安全的

关于这个呢,你说它算一个问题,它也的确算个问题。 说它不算问题呢,又的确可以有很多方法规避它,而且系统的并发程度越高,这个问题才越明显。 但每个解决方法都有自己的不足之处:

  • 用局部变量呢,每个调用都要创建对象。造成很多不必要的开销
  • synchronized呢,并发高了又顶不住
  • ThreadLocal呢,还要手动创建每个thread的实例。thread多起来了,实例又变多。如果一直不销毁thread又不remove,内存占用又变大。

我的需求只是需要一个工具按照某个格式来format和parse一个时间。怎么就这么难呢。


新API是真的香

香!那是真的香!香的不行,逢人就推! 新API能解决上面说的全部问题。

新API的特性:

  • 不可变和线程安全的类。java.time核心包下的类(瞬时、持续时间、时间、日期、时区、时间段)。都是不可变且线程安全的。
  • 以“域”为基准的设计(Domain-driven design)。新API可以很精准的表达一个日期或者时间的概念。以“域”为基准的设计,能够让我们更加容易理解一个“时间”的精确度。比如你只需要一个“日期”的时候,你根本就不用关心“具体时间”。这个主要区别于java.util.Date只能表示时间轴上的一个瞬间。
  • 分离不同的历法。新API默认基于ISO体系(ISO-8601)的公元纪年历。但同时也支持其它4中的历法系统(泰国佛教历、日本历、中华民国历、伊斯兰历)。但是不支持中国农历

新时间日期API的包java.time,大部分情况只需要用到java.timejava.time.format

时间和日期类

  • java.time.Instant。存储了一个时间瞬间,与旧API的Date最相似。这个类用long存储了从公元纪0年至今的秒和用int存储了当前时间的纳秒。
  • java.time.LocalDate,存储了一个ISO-8601的日期和时间。通常被视为年-月-日,如2007-12-03
  • java.time.LocalTime,存储了一个ISO-8601的时间,精度为纳秒。通常被视为时:分:秒,如10:15:30
  • java.time.LocalDateTime, 存储了一个ISO-8601的日期和时间,精度为纳秒。通常被视为年-月-日-时-分-秒,如2007-12-03T10:15:30
  • java.time.ZonedDateTime,存储了一个ISO-8601 带时区的日期和时间。如果不需要时区的信息的话,不要用这个类

持续时间类

  • java.time.Duration。表示一段持续时间,精度为纳秒,存储跟Instant类似。
  • java.time.Period。表示一段时间,精度为天。

附加类

  • java.time.Month。存储一个月
  • java.time.DayOfWeek存储一周中的一天
  • java.time.Year单独存储年
  • java.time.YearMonth存储年和月
  • java.time.MonthDay存储一个月和一天,没有年份或时间。它存储一个月和一个月中的某天,例如"–12-03",并且可以用于存储年度事件(例如生日)而不存储年份。
  • java.time.OffsetTime存储时间和相对于UTC的偏移。如"11:30+01:00"
  • java.time.OffsetDateTime存储日期和时间相对于UTC的偏移。如"2010-12-03T11:30+01:00"

创建实例

官方推荐使用各个类的静态工厂方法来创建实例,或者通过其他时间日期类的方法来创建实例。

用于创建实例的静态工厂方法前缀

  • now. 就是以当前时间创建实例
  • of. 用一些给定的值来创建实例
  • from. 从一个更细粒度的域实例,获取创建当前类型实例所需的值。
  • parse. 从文本解析。默认是采用ISO-8601标准,也可以用自己定义的pattern的DateTimeFormatter

操作实例

注意,新API中所有时间日期类都是不可变的。

  • get. 获得某个值
  • is. 判断某个条件
  • with. 修改某个值。语义上和set是一样的。只是说为了区别出原对象不可变。with还能通过传入一个TemporalAdjuster来修改为某些特定的时间日期
  • plus. 加某个值
  • minus. 减某个值
  • to. 将此实例转换成别的域实例(粒度只减不增). 会损失某些域的值
  • at. 将此实例与其他域的实例结合(粒度只增不减),生成新的实例

域调整 TemporalAdjuster

Temporal,其实就是一个广义的"时间"LocalDateLocalDateTimeInstant,这些都是Temporal。 Adjuster,就是调整器。 所以顾名思义,TemporalAdjuster就是用来"调整时间"的一种手段。

Java 已经实现了TemporalAdjusters,这个里面包含了很多预设的时间调整操作。

时间段

  • Duration, 精度为纳秒。可以指定"时间长度",或者通过between()获得两个时间之间的间隔。
  • Period,精度为天。跟Duration没有本质的区别。

其它历法

官方推荐在操作时间的时候,尽可能的是用ISO标准的时间以简化操作,降低复杂性。 只有在需要本地化输出其它历法的时候,才使用其它历法。

参考