概述
Java8 一个新增的重要特性就是引入了新的时间和日期 API,它们被包含在java.time 包中。借助新的时间和日期 API 可以以更简洁的方法处理时间和日期。
在Java8 之前,所有关于时间和日期的API都存在各种使用方面的缺陷,主要有:
- Java 的
java.util.Date
和java.util.Calendar
类易用性差,不支持时区,并且是可变的,也就意味着他们都不是线程安全的;
- 用于格式化日期的类
DateFormat
被放在java.text
包中,它是一个抽象类,所以我们需要实例化一个SimpleDateFormat
对象来处理日期格式化,并且DateFormat
也是非线程安全,这意味着如果你在多线程程序中调用同一个DateFormat
对象,会得到意想不到的结果。
- 对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,这意味着从
Calendar
中获取的月份需要加一才能表示当前月份。
由于以上这些问题,出现了一些三方的日期处理框架,例如 Joda-Time,date4j 等开源项目。但是,Java 需要一套标准的用于处理时间和日期的框架,于是 Java8 中引入了新的日期API。
旧时间 API 的缺陷
时间对象的可变性
旧的时间 API 是可变的
1
2
3
4
5
6
7
8
9
|
@Test
public void testDateChange() {
Date date = new Date();
System.out.println("当前时间:");
System.out.println(date);
date.setTime(date.getTime() + 20000);
System.out.println("当前时间:");
System.out.println(date);
}
|
输出
当前时间:
Fri Jul 19 17:16:29 CST 2019
当前时间:
Fri Jul 19 17:16:49 CST 2019
此时,在多线程情况下尤要注意,不要操作同一个Date
对象,避免将Date
对象作为引用参数传递。
新的时间 API 的不可变的
1
2
3
4
5
6
7
8
9
10
|
@Test
public void testDateNoChange() {
LocalDateTime date = LocalDateTime.now();
System.out.println("当前时间:");
System.out.println(date);
LocalDateTime plusDays = date.plusDays(1);
System.out.println("当前时间:");
System.out.println(date);
}
|
输出
当前时间:
2019-07-19T17:17:16.431
当前时间:
2019-07-19T17:17:16.431
线程安全性
注:此处引入 Google Guava 的 jar 包
旧的时间 API 是线程不安全的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Test
public void testThread() {
ExecutorService executorService = Executors.newCachedThreadPool();
List<String> dateStrList = Lists.newArrayList("2018-04-01 10:00:01", "2018-04-02 11:00:02",
"2018-04-03 12:00:03", "2018-04-04 13:00:04", "2018-04-05 14:00:05");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for (String str : dateStrList) {
// 此处使用Java8的箭头函数
executorService.execute(() -> {
try {
simpleDateFormat.parse(str);
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
|
结果抛出异常
新的时间 API 是线程安全的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Test
public void testOkThread() {
ExecutorService executorService = Executors.newCachedThreadPool();
List<String> dateStrList = Lists.newArrayList("2018-04-01 10:00:01", "2018-04-02 11:00:02",
"2018-04-03 12:00:03", "2018-04-04 13:00:04", "2018-04-05 14:00:05");
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (String str : dateStrList) {
// 此处使用Java8的箭头函数
executorService.execute(() -> {
try {
dtf.parse(str);
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
|
结果正常
操作的便捷性
以获取当前月的最后一天为例Java7 的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static String getLastDayOfMonth(int year, int month) {
Calendar cal = Calendar.getInstance();
// 设置年份
cal.set(Calendar.YEAR, year);
// 设置月份
cal.set(Calendar.MONTH, month - 1);
// 获取某月最大天数
int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
// 设置日历中月份的最大天数
cal.set(Calendar.DAY_OF_MONTH, lastDay);
// 格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String lastDayOfMonth = sdf.format(cal.getTime());
return lastDayOfMonth;
}
|
可以看到十分麻烦
Java8 的方式
1
|
String dateStr = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).toString();
|
示例
新旧 API 之间的转化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 日期格式之间的转化
*
* @return void
* @author fanmingyong
* @date Jul 19, 2019 4:54:03 PM
* @Modify
*/
@Test
public void transform() {
// LocalDateTime 转 Date
Instant instant = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
Date.from(instant);
// Date 转 LocalDateTime
new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
// LocalDate 转 LocalDateTime
LocalDateTime localDateTime = LocalDate.now().atTime(12, 12);
// LocalDateTime 转 LocalDate
LocalDate localDate = LocalDateTime.now().toLocalDate();
}
|
如何获取一个时间段
1
2
3
4
|
// 获取一段时间差
LocalDate to = LocalDate.now().plusDays(1);
Period period = Period.between(LocalDate.now(), to);
System.out.println(period.getDays());
|
高级用法
TemporalAdjusters
类中包含了很多静态方法可以直接使用,如果默认的方法不能满足你的需求,你还可以创建自定义的TemporalAdjuster
接口的实现,TemporalAdjuster
也是一个函数式接口,所以我们可以使用 Lambda 表达式:
1
2
3
4
|
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
|
比如给定一个日期,计算该日期的下一个工作日(不包括星期六和星期天):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Test
public void use() {
LocalDate date = LocalDate.of(2019, 7, 19);
date.with(temporal -> {
// 当前日期
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
// 正常情况下,每次增加一天
int dayToAdd = 1;
// 如果是星期五,增加三天
if (dayOfWeek == DayOfWeek.FRIDAY) {
dayToAdd = 3;
}
// 如果是星期六,增加两天
if (dayOfWeek == DayOfWeek.SATURDAY) {
dayToAdd = 2;
}
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
System.out.println(date);
}
|
使 Mybatis 支持 Java8 的新 API
只需假如如下依赖,即可支持实体类中的LocalDate/LocalDateTime
类型的属性
1
2
3
4
5
|
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.1</version>
</dependency>
|