概述

Java8 一个新增的重要特性就是引入了新的时间和日期 API,它们被包含在java.time 包中。借助新的时间和日期 API 可以以更简洁的方法处理时间和日期。 在Java8 之前,所有关于时间和日期的API都存在各种使用方面的缺陷,主要有:

  1. Java 的java.util.Datejava.util.Calendar类易用性差,不支持时区,并且是可变的,也就意味着他们都不是线程安全的;
  2. 用于格式化日期的类DateFormat被放在java.text包中,它是一个抽象类,所以我们需要实例化一个SimpleDateFormat对象来处理日期格式化,并且DateFormat也是非线程安全,这意味着如果你在多线程程序中调用同一个DateFormat对象,会得到意想不到的结果。
  3. 对日期的计算方式繁琐,而且容易出错,因为月份是从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>