【小家java】java8新特性之---全新的日期、时间API(JSR 310规范),附SpringMVC、Mybatis中使用JSR310的正确姿势(上)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【小家java】java8新特性之---全新的日期、时间API(JSR 310规范),附SpringMVC、Mybatis中使用JSR310的正确姿势(上)

相关阅读

【小家java】java5新特性(简述十大新特性) 重要一跃

【小家java】java6新特性(简述十大新特性) 鸡肋升级

【小家java】java7新特性(简述八大新特性) 不温不火

【小家java】java8新特性(简述十大新特性) 饱受赞誉

【小家java】java9新特性(简述十大新特性) 褒贬不一

【小家java】java10新特性(简述十大新特性) 小步迭代

【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本


【小家java】java8新特性之—Base64加密和解密原理

【小家java】java8新特性之—反射获取方法参数名

【小家java】java8新特性之—全新的日期、时间API(完全实现了JSR 310规范)

【小家java】java8新特性之—Optional的使用,避免空指针,代替三目运算符

【小家java】java8新特性之—lambda表达式的的原理

【小家java】java8新特性之—函数式接口(Supplier、Consumer、Predicate、Function、UnaryOperator,通往高阶设计的好工具)

【小家java】java8新特性之—方法引用

【小家java】java8新特性之—Stream API 详解 (Map-reduce、Collectors收集器、并行流)

【小家java】java8新特性之—外部迭代和内部迭代(对比性能差异)


每篇一句


男人要么帅一点,要么努力一点。如果你又帅又努力,那就可以拽一点


Java8之前的日期、时间现状

Tiago Fernandez做了一个很有意思的投票,统计对Java API的不满意程度,最终Java Date/Time/Calendar API被评为最烂API第二名(第一为XML/DOM)。


Java三次引入处理时间的API,JDK1.0中包含了一个Date类,但大多数方法在java1.1引入Calendear类之后被弃用了。

它的实例都是可变的,而且它的API很难使用,比如月份是从0开始这种反人类的设置。不止如此,还有如下的一些使用不方便的地方


其实JSR310的规范领导者Stephen Colebourne,同时也是Joda-Time的创建者,JSR310是在Joda-Time的基础上建立的,参考了绝大部分的API,但并不是说JSR310=JODA-Time,还是有好些区别的


    Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期累,此外用于格式化和解析的类在java.text包中定义。


    Java 8之前老版的 java.util.Date 类以及其他用于建模日期时间的类有很多不一致及 设计上的缺陷,包括易变性以及糟糕的偏移值、默认值和命名


    java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql并不合理。

public static void main(String[] args) {
         java.util.Date date = new Date(System.currentTimeMillis());
         java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
         System.out.println(date); //Sat Aug 04 10:35:40 CST 2018
         System.out.println(sqlDate); //2018-08-04
    }

      对于时间、时间戳、格式化以及解析,并没有明确定义的类。对于格式化和解析的需求,有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求(关键是它还不是线程安全的)。


     日期类国际化支持的并不是很好


关于日期定义的一些常识


现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前, 但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离。所以我们要表示时间, 就需要人为定义一个原点。


原点被规定为,格林威治时间(GMT)1970年1月1日的午夜 为起点,之于为啥是GMT时间,大概是因为本初子午线在那的原因吧。


Java8中日期、时间类的概述

Java8时间API最重要的几个类:


image.png


所有类都实现了 Temporal 接口, Temporal 接口定义了如何读取和操纵


java8引入了一套全新的时间日期API。java.time包中的是类是不可变且线程安全的。新的时间及日期API位于java.time中,下面是一些关键类

●Instant——它代表的是时间戳(另外可参考Clock类)

●LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。

●LocalTime——它代表的是不含日期的时间

●LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。

●ZonedDateTime——这是一个包含时区的完整的日期时间还有时区,偏移量是以UTC/格林威治时间为基准的。

●Timezones——时区。在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。


Java8日期、时间API特点和使用的设计模式

    不变性:新的日期/时间API中,所有的类都是不可变的,这对多线程环境有好处。


     **关注点分离(这点个人认为在设计中非常非常重要):**新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。


     清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用now()方法,在所有的类中都定义了format()和parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式(策略模式在设计一整套东西的时候,特别有效,可以对开发者友好),一旦你使用了其中某个类的方法,与其他类协同工作并不困难。


     实用操作(相当于很多工具方法,不再需要我们自己封装了):所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。

     TemporalAdjuster 让你能够用更精细的方式操纵日期,不再局限于一次只能改变它的 一个值,并且你还可按照需求定义自己的日期转换器


Java8日期、时间API包介绍


     **java.time包:**这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。


     **java.time.chrono包:**这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。


     **java.time.format包:**这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。


     **java.time.temporal包:**这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。


     **java.time.zone包:**这个包包含支持不同时区以及相关规则的类

Java8常用的类介绍

Instant和Clock


Instant它是精确到纳秒的(而不是象旧版本的Date精确到毫秒,System.nanoTime是精确到纳秒级别了),如果使用纳秒去表示一个时间则原来使用一位Long类型是不够的,需要占用更多一点的存储空间,所以它内部是用两个字段去存储的。第一个部分保存的是自标准Java计算时代(就是1970年1月1日开始)到现在的秒数,第二部分保存的是纳秒数(永远不会超过999,999,999)


在新的时间API中,Instant表示一个精确的时间点,Duration和Period表示两个时间点之间的时间量(所以我们比较两个时间差,用新API更方便了,后面会有示例)。

Instant表示一个精确的时间,时间数轴就是由无数个时间点组成,数轴的原点就是上面提 到的1970-1-1 00:00:00,Instant由两部分组成,一是从原点开始到指定时间点的秒数s(用long存储), 二是距离该秒数s的纳秒数(用int存储)。源码:


private static final long MIN_SECOND = -31557014167219200L;
private static final long MAX_SECOND = 31556889864403199L;
//还定义了两个最大、最小时间的常量,我们以后可以直接使用
public static final Instant MIN = Instant.ofEpochSecond(MIN_SECOND, 0);
public static final Instant MAX = Instant.ofEpochSecond(MAX_SECOND, 999_999_999);
//引用一个long和一个int来存储秒和距离秒的纳秒
private final long seconds;
private final int nanos;
//我们会发现 now的底层,调用的其实是Clock的方法
public static Instant now() {
   return Clock.systemUTC().instant();
}
//策略模式,自带parse方法,把字符串解析成Instant
public static Instant parse(final CharSequence text) {
        return DateTimeFormatter.ISO_INSTANT.parse(text, Instant::from);
    }
//优雅的比较方案:
 public boolean isAfter(Instant otherInstant) {
     return compareTo(otherInstant) > 0;
 }
 public boolean isBefore(Instant otherInstant) {
     return compareTo(otherInstant) < 0;
 }

下面看获取当前时间戳的几个方法:


public static void main(String[] args) {
        Instant now = Instant.now();
        System.out.println(now); //2018-08-04T06:35:59.354Z
        System.out.println(now.getEpochSecond()); //1533364559
        System.out.println(now.getNano()); //354000000
        //下面是几种获取时间戳(毫秒值)的方法 推荐使用高逼格的toEpochMilli()去做
        System.out.println(now.toEpochMilli());
        System.out.println(System.currentTimeMillis());
        System.out.println(new Date().getTime());
    }
还有一些对plus、minus、isAfter、isBefore等方法,此处不做多余讲解

下面介绍两个比较实用的方法:

public static void main(String[] args) {
        //自带的解析 若需要自定义格式,可以这么来
        Instant temp =Instant.parse("2007-12-03T10:15:30.00Z");
        Instant now = Instant.now();
        Instant instant = now.plusSeconds(TimeUnit.HOURS.toSeconds(25));
        //希望得到两个时间戳,他们相隔了几个小时、几天、几个月?
        System.out.println(now.until(instant,ChronoUnit.HOURS)); //25
        System.out.println(now.until(instant,ChronoUnit.DAYS)); //1(这里显示1不是2哦)
        System.out.println(instant.until(now,ChronoUnit.HOURS)); //-25(注意,这里是负数哦)
    }

以前我们要统计一段程序的运行时间,现在可以采用这种优雅的方式了

Instant start = Instant.now();
doSomething();
Instant end = Instant.now();
//计算时间差 采用Duration来处理时间戳的差
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
System.out.println("millis = " + millis);

大概300年的纳秒值会导致long值溢出。所以毫秒值用long存储,永远都不会溢出


java.time.Duration表示一段时间。所以像电影持续多久,要做同步字幕的话,用这个类可以很好的解决问题。Duration可以进行multipliedBy()乘法和dividedBy()除法运算。negated()做取反运算,即1.2秒取反后为-1.2秒。


简单的说下clock:时钟提供给我们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。


Clock c1 = Clock.systemUTC(); //系统默认UTC时钟(当前瞬时时间 System.currentTimeMillis())
//这么来会采用系统默认的时区
Clock c2 = Clock.systemDefaultZone(); //系统默认时区时钟(当前瞬时时间)
//输出那两个能看到效果
System.out.println(c1); //SystemClock[Z]  这个其实用得最多
System.out.println(c2); //SystemClock[Asia/Shanghai]
//可以获取到和时区敏感的对象
Clock c3 = Clock.system(ZoneId.of("Europe/Paris")); //巴黎时区
Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); //相对于系统默认时钟两秒的时钟

LocalDate(本地日期)

上面介绍的Instant是一个绝对的准确时间点,是人类不容易理解的时间,现在介绍人类使用的时间。


API的设计者推荐使用不带时区的时间,除非真的希望表示绝对的时间点。


可以使用静态方法now()和of()创建LocalDate。

public static void main(String[] args) {
        //获取当前日期
        LocalDate now = LocalDate.now();
        //2017-01-01
        LocalDate newYear = LocalDate.of(2017, 1, 1);
        System.out.println(now); //2018-08-04
        System.out.println(newYear); //2017-01-01
    }
//显然,内置很多plus、minus的基本计算。with方法相当于修改,但返回的是一个新的日期对象哦
//三天后
now.plusDays(3);
//一周后
now.plusWeeks(1)
//两天前 
now.minusDays(2)
//备注:增加一个月不会出现2017-02-31 而是会返回该月的最后一个有效日期,即2017-02-28,这点特别的人性化有木有
LocalDate.of(2017, 1, 31).plusMonths(1)

LocalDate对应的表示时间段的是Period, Period内部使用三个int值分表表示年、月、日。 Duration和Period都是TemporalAmount接口的实现,该接口表示时间量

LocalDate 也可以增加或减少一段时间(自由度更高):


//2019-02-01
feb.plus(Period.ofYears(2));
//2015-02-01
feb.minus(Period.ofYears(2);
//使用until获得两个日期之间的Period对象 
feb.until(LocalDate.of(2017, 2, 10));//输出---> P9D
//提供isLeapYear判断是否是闰年,这个太友好了
//DayOfWeek 是个枚举,并且实现了TemporalAccessor/TemporalAdjuster接口,所以也可以直接plus,minus等,非常方便。  Java8还提供了Year MonthDay YearMonth来表示部分日期,例如MonthDay可以表示1月1日。
LocalDate.of(2017, 1, 1).getDayOfWeek();
DayOfWeek.SUNDAY.plus(2); //TUESDAY

LocalTime(本地时间)


LocalTime表示一天中的某个时间,例如18:00:00。LocaTime与LocalDate类似,他们也有相似的API。所以这里不做详细介绍了


public static void main(String[] args) {
        LocalTime now = LocalTime.now();
        LocalTime evning = LocalTime.of(21, 0);
        System.out.println(now); //17:03:13.728
        System.out.println(evning); //10:00
    }

LocalDateTime(本地日期和时间)


LocalDateTime表示一个日期和时间,它适合用来存储确定时区的某个时间点。不适合跨时区的问题。

public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime of = LocalDateTime.of(LocalDate.now(), LocalTime.now());
        System.out.println(now); //2018-08-04T18:33:37.478
        System.out.println(of); //2018-08-04T18:33:37.478
    }

ZonedDateTime(带时区的 日期和时间)


Java8使用ZoneId来标识不同的时区.


public static void main(String[] args) {
        //获得所有可用的时区  size=600 这个数字不是固定的
        Set<String> allZones = ZoneId.getAvailableZoneIds();
        //获取默认ZoneId对象 系统当前所在时区
        ZoneId defZoneId = ZoneId.systemDefault();
        //获取指定时区的ZoneId对象
        ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
        //ZoneId.SHORT_IDS返回一个Map<String, String> 是时区的简称与全称的映射。下面可以得到字符串 Asia/Shanghai
        String shanghai = ZoneId.SHORT_IDS.get("CTT");
        System.out.println(shanghai); //Asia/Shanghai
    }

IANA(Internet Assigned Numbers Authority,因特网拨号管理局)维护着一份全球所有已知的时区数据库,

每年会更新几次,主要处理夏令时规则的改变。Java使用了IANA的数据库。

public static void main(String[] args) {
        //2017-01-20T17:35:20.885+08:00[Asia/Shanghai]
        ZonedDateTime now = ZonedDateTime.now();
        //2017-01-01T12:00+08:00[Asia/Shanghai]
        ZonedDateTime of = ZonedDateTime.of(2017, 1, 1, 12, 0, 0, 0, ZoneId.of("Asia/Shanghai"));
        //使用一个准确的时间点来创建ZonedDateTime,下面这个代码会得到当前的UTC时间,会比北京时间早8个小时
        ZonedDateTime utc = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC"));
        System.out.println(now); //2018-08-04T18:53:24.686+08:00[Asia/Shanghai]
        System.out.println(of); //2017-01-01T12:00+08:00[Asia/Shanghai]
        System.out.println(utc); //2018-08-04T10:53:24.687Z[UTC]
    }

ZonedDateTime的许多方法与LocalDateTime、LocalDate、LocalTime类似


LocalDateTime转换为带时区的ZonedDateTime


//atZone方法可以将LocalDateTime转换为ZonedDateTime,下面的方法将时区设置为UTC。
//假设现在的LocalDateTime是2017-01-20 17:55:00 转换后的时间为2017-01-20 17:55:00[UTC]
LocalDateTime.now().atZone(ZoneId.of("UTC"));
//使用静态of方法创建zonedDateTime
ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("UTC"));

实用常量们

 

public static void main(String[] args) {
        //Instant的常量
        System.out.println(Instant.MIN); //-1000000000-01-01T00:00:00Z
        System.out.println(Instant.MAX); //+1000000000-12-31T23:59:59.999999999Z
        //LocaDate的常量
        System.out.println(LocalDate.MIN); //-999999999-01-01
        System.out.println(LocalDate.MAX); //+999999999-12-31
        //LocalTime的常量
        System.out.println(LocalTime.MIN); //00:00
        System.out.println(LocalTime.MAX); //23:59:59.999999999
        System.out.println(LocalTime.MIDNIGHT); //00:00
        System.out.println(LocalTime.NOON); //12:00
        //LocalDateTime的常量
        System.out.println(LocalDateTime.MIN); //-999999999-01-01T00:00
        System.out.println(LocalDateTime.MAX); //+999999999-12-31T23:59:59.999999999
        //ZoneOffset的常量
        System.out.println(ZoneOffset.UTC); //Z
        System.out.println(ZoneOffset.MIN); //-18:00
        System.out.println(ZoneOffset.MAX); //+18:00
        //ZoneId的常量
        System.out.println(ZoneId.SHORT_IDS); //{CTT=Asia/Shanghai, ART=Africa/Cairo, CNT=America/St_Johns, PRT=America/Puerto_Rico
    }
相关文章
|
6天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
20 2
|
13天前
|
缓存 监控 Java
如何运用JAVA开发API接口?
本文详细介绍了如何使用Java开发API接口,涵盖创建、实现、测试和部署接口的关键步骤。同时,讨论了接口的安全性设计和设计原则,帮助开发者构建高效、安全、易于维护的API接口。
36 4
|
21天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
6月前
|
设计模式 前端开发 JavaScript
Spring MVC(一)【什么是Spring MVC】
Spring MVC(一)【什么是Spring MVC】
|
5月前
|
设计模式 前端开发 Java
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
75 1
|
5月前
|
前端开发 Java 应用服务中间件
Spring框架第六章(SpringMVC概括及基于JDK21与Tomcat10创建SpringMVC程序)
Spring框架第六章(SpringMVC概括及基于JDK21与Tomcat10创建SpringMVC程序)
|
6月前
|
前端开发 Java 关系型数据库
基于ssm框架旅游网旅游社交平台前后台管理系统(spring+springmvc+mybatis+maven+tomcat+html)
基于ssm框架旅游网旅游社交平台前后台管理系统(spring+springmvc+mybatis+maven+tomcat+html)
|
5月前
|
XML Java 数据格式
SpringMVC的XML配置解析-spring18
SpringMVC的XML配置解析-spring18
|
5月前
|
应用服务中间件
从代码角度戳一下springMVC的运行过程-spring16
从代码角度戳一下springMVC的运行过程-spring16
|
前端开发 Java Go
Spring MVC 和 Spring Boot 的区别
Spring MVC 和 Spring Boot 的区别
219 0