java.util.Date不香吗?我为什么要用新API?需求都写完了吗?
旧API能用,但真的不香
提到Java的日期,很多人第一时间就是java.util.Date和java.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.time和java.time.format。
时间和日期类
java.time.Instant。存储了一个时间瞬间,与旧API的Date最相似。这个类用long存储了从公元纪0年至今的秒和用int存储了当前时间的纳秒。java.time.LocalDate,存储了一个ISO-8601的日期和时间。通常被视为年-月-日,如2007-12-03java.time.LocalTime,存储了一个ISO-8601的时间,精度为纳秒。通常被视为时:分:秒,如10:15:30java.time.LocalDateTime, 存储了一个ISO-8601的日期和时间,精度为纳秒。通常被视为年-月-日-时-分-秒,如2007-12-03T10:15:30java.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,其实就是一个广义的"时间"。LocalDate、LocalDateTime、Instant,这些都是Temporal。
Adjuster,就是调整器。
所以顾名思义,TemporalAdjuster就是用来"调整时间"的一种手段。
Java 已经实现了TemporalAdjusters,这个里面包含了很多预设的时间调整操作。
时间段
Duration, 精度为纳秒。可以指定"时间长度",或者通过between()获得两个时间之间的间隔。Period,精度为天。跟Duration没有本质的区别。
其它历法
官方推荐在操作时间的时候,尽可能的是用ISO标准的时间以简化操作,降低复杂性。 只有在需要本地化输出其它历法的时候,才使用其它历法。