浅谈JDK8新特性

Java8(又称为 JDK8)是 Java 语言开发的一个重要版本,于2014年发布。 JDK8 可以说是里程碑式的版本,提供了很多的新功能,支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。 本文仅涉及编程开发中常用的特性

涉及 说明
Lambda表达式 允许把函数作为参数传递进方法中。
方法引用 函数引用,替代lambda。
函数式接口 只有一个抽象方法的接口,可以接收函数。
Stream API 流式处理,函数式编程示例。
默认方法 接口提供默认实现方法。
Optional API 值容器,简化空指针处理。
Date-Time API 新的日期接口。

函数式编程

JDK8 提供了函数式编程支持,不再是单纯的面对对象。

函数编程语言 最重要的基础是λ演算(lambda calculus)。而λ演算的函数可以接受函数当作输入和输出。

JDK8 提供了一些特性来支持函数式编程:Lambda表达式 , 函数式接口 , 方法引用 等。

这三者具有紧密联系,需要结合理解。 默认方法stream API增强API 等则另行说明。

  • 函数式接口 只有一个抽象方法的接口。
  • Lambda表达式 只能作用于 函数式接口
  • Lambda表达式 表示的逻辑已经被抽取为方法, 方法引用 可以替换 Lambda表达式 ,简化代码。

Lambda 表达式

Lambda Expressions : 允许把函数作为参数传递进方法中。主要用来定义行内执行的 函数式接口

lambda表达式 :又名 闭包匿名方法

函数式接口 :只有一个抽象方法的接口。

语法格式

1
2
3
4
// 格式一
(parameters) -> expression
// 格式二
(parameters) ->{ statements; }

语法特征:

  • 可选的参数类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但没有参数或多个参数需要定义圆括号。
  • 可选的参数大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

基本语法特征示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package tk.gushizone.jdk8.lambda;

import org.junit.Test;

public class LambdaTest {

/**
* 带返回值的lambda表达式:四则运算
*/
@Test
public void test(){
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;

System.out.println("10 + 5 = " + LambdaTest.operate(10, 5, addition));
System.out.println("10 - 5 = " + LambdaTest.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + LambdaTest.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + LambdaTest.operate(10, 5, division));
}

interface MathOperation {
int operation(int a, int b);
}

private static int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}

变量作用域

1
2
3
public interface Converter<T1, T2> {
void convert(int i);
}

Lambda表达式 的局部变量具有隐性的 final 的语义。

可以不用声明为 final,但是必须不可被后面的代码修改。

1
2
3
4
5
6
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);

// ERROR : Local variable num defined in an enclosing scope must be final or effectively final
num = 5;

在 Lambda表达式 当中不允许声明一个与局部变量同名的参数或者局部变量。

1
2
3
4
String first = "";

// ERROR : Variable 'first' is already defined in the scope
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());

方法引用

Method references : 通过方法的名字来指向一个方法。可以理解为Lambda表达式的缩写,可以使代码更加简洁。

引用的方法需要与函数式接口具有一样的参数和返回值。

语法格式

引用类型 语法格式
构造器方法引用 Class::new
类静态方法引用 Class::method
实例特殊方法引用(第一个参数,是调用者,第二个参数(或无参)是参数) Class::method
实例方法引用 instance::method

方法引用的语法格式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Car {

/** 创造 **/
public static Car create(Supplier<Car> supplier) {
return supplier.get();
}

/** 碰撞 **/
public static void collide(Car car) {
System.out.println("Collided " + car.toString());
}

/** 跟随 **/
public void follow( Car another) {
System.out.println(this.toString() + " Following the " + another.toString());
}

/** 修理 **/
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test(){

// 1. 构造器方法引用(调用默认构造器) : Class::new,或 Class<T>::new
Car car1 = Car.create( Car::new );
Car car2 = Car.create( Car::new );
Car car3 = Car.create( Car::new );
List< Car > cars = Arrays.asList( car1, car2, car3 );

// 2. 类静态方法引用 : Class::method
cars.forEach( Car::collide );

// 3. 类的普通无参方法才能被引用 : Class::method
cars.forEach( Car::repair );

// 4. 实例方法引用 : instance::method
Car police = Car.create( Car::new );
cars.forEach( police::follow );

}
1
2
3
4
5
6
7
8
9
/**
* 数组构造引用
*/
@Test
public void arrayConstructor() {

// 数组构造引用
Function<Integer, int[]> function = int[]::new;
}

函数式接口

functional interfaces : 仅有一个抽象方法的接口,可以接收函数。

可以有多个非抽象方法,函数式接口可以包含Object的public方法(即使它们是抽象方法),因为任何一个函数式接口的实现,默认都继承了 Object 类,包含了来自 java.lang.Object 里对这些抽象方法的实现。

JDK8 引入了新注解 @FunctionalInterface ,用于函数式接口的编译级错误检查(非必须)。

常用函数式接口

使用 lambda表达式 就需要函数式接口,除了现有的函数式接口,JDK8还提供了 java.util.function包 ,其包含一些常用的函数式接口。

常用接口 说明
Function<T,R> 函数,一个参数 T ,一个返回值 R
Supplier<T> 供应者,无参数,返回一个结果 T
Consumer<T> 消费者,一个参数 T ,无返回结果。
Predicate<T> 断言,一个参数 T,返回 boolean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package tk.gushizone.java.jdk8.functioninterface;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
* @author gushizone@gmail.com
* @date 2020-05-31 22:43
*/
public class FunctionInterfaceUtils {

public static <T, R> List<R> function(Collection<T> collection, Function<T, R> function) {
if (CollectionUtils.isEmpty(collection)) {
return Collections.emptyList();
}
return collection.stream()
.map(function)
.collect(Collectors.toList());
}

@SafeVarargs
public static <E> void consumer(Consumer<E> consumer, E... array) {
if (ArrayUtils.isEmpty(array)) {
return;
}
for (E e : array) {
consumer.accept(e);
}
}

public static <T, C extends Collection<T>> C supplier(Collection<T> collection, Supplier<C> supplier) {
if (CollectionUtils.isEmpty(collection)) {
return supplier.get();
}
return collection.stream()
.collect(Collectors.toCollection(supplier));
}

public static <T> List<T> predicate(Collection<T> collection, Predicate<T> predicate) {
if (CollectionUtils.isEmpty(collection)) {
return Collections.emptyList();
}
return collection.stream()
.filter(predicate)
.collect(Collectors.toList());
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package tk.gushizone.java.jdk8.functioninterface;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import tk.gushizone.java.jdk8.entity.User;

import java.util.LinkedHashSet;
import java.util.List;

/**
* @author gushizone@gmail.com
* @date 2020-05-31 22:44
*/
@Slf4j
public class FunctionInterfaceTest {

private static final List<User> LIST = Lists.newArrayList(new User("Foo", 16),
new User("Bar", 18), new User("FooBar", 14));

/**
* function : 一个入参,一个出参
*/
@Test
public void function() {

List<String> results = FunctionInterfaceUtils.function(LIST, User::getUsername);
log.info(results.toString());
}

/**
* consumer : 一个入参
*/
@Test
public void consumer () {

StringBuilder result = new StringBuilder();
FunctionInterfaceUtils.consumer(result::append, "b", "c", "a");
log.info(result.toString());
}

/**
* supplier : 一个出参
*/
@Test
public void supplier() {

LinkedHashSet<User> results = FunctionInterfaceUtils.supplier(LIST, LinkedHashSet::new);
log.info(results.toString());
}


/**
* predicate : 一个入参 布尔返回值
*/
@Test
public void predicate() {

List<User> results = FunctionInterfaceUtils.predicate(LIST, e -> e.getAge() >= 18);
log.info(results.toString());
}

}

Stream API

Stream(流) : 一个来自数据源的元素队列并支持聚合操作。

  • 元素 : 特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 : 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 : 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。和以前的Collection操作不同, Stream操作还有两个基础的特征:
    • Pipelining : 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
    • 内部迭代 : 以前对集合遍历都是通过 Iterator 或者 For-Each 的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

得益于 默认方法Stream方法 被整合到了 Collection接口 中。现在可以使用类似 SQL 方式操作元素集合,十分高效简洁。

Stream操作 的一般流程 : 流化 -> 中间操作 -> 完成操作

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void simpleCase() {

List<String> strings = Arrays.asList("bc", "efg", "abc", "", "jkl");

List<String> results = strings.stream()
.filter(string -> !string.isEmpty())
.sorted()
.collect(Collectors.toList());

// [abc, bc, efg, jkl]
System.out.println(results);
}

流化

JDK8 中,集合接口提供了两种方法生成流。

涉及方法 说明
stream() 为集合创建串行流。
parallelStream() 为集合创建并行流,相当于多线程操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void stream() {

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

// 12345
list.stream().sorted().forEach(System.out::print);
System.out.println();
// 31542 -- 顺序不定
list.parallelStream().forEach(System.out::print);
System.out.println();
// 12345
list.parallelStream().forEachOrdered(System.out::print);
}

中间操作

涉及 说明 示例
filter(..) 筛选出元素。 .filter(String::isEmpty) 为筛选出所有空字符串。
map(..) 映射每个元素到对应的结果。 .map(i -> i * i) 为获取获取对应的平方数。
sorted() / sorted(..) 排序(升序),通过 Comparable接口 判断。 .sorted(Comparator.reverseOrder()) 为降序,可传入 lambda表达式。
distinct() 通过 equals方法 判断。 -
count() 统计个数。 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void middle() {

List<Integer> list = Arrays.asList(3, 2, 2, 9, 7, 1, 5);

List<Integer> results = list.stream()
.distinct()
.map(i -> i * i)
.sorted()
.limit(5)
.collect(Collectors.toList());

// [1, 4, 9, 49, 81]
System.out.println(results);
}

完成操作

常见的 完成操作归约操作 ,即转换类型。

涉及 说明 示例
collect(..) 收集归约。一般为最终操作,配合 Collectors 使用。 .collect(Collectors.toList()) 为收集列表
reduce(..) 聚合归约。一般为最终操作,可以实现常见的计算。 .reduce(BigDecimal.ZERO, BigDecimal::add) 为累加求和。

collect

collect 接收一个 Collector 参数。Collectors 类实现了很多收集归约操作,例如将流转换成集合和聚合元素。

涉及 说明 示例
toList() / toSet() / toMap() / toCollection(..) 转换成集合。 Collectors.toCollection(HashSet::new)
joining(..) 转换成字符串。 Collectors.joining(", ") / Collectors.joining(", ", "{", "}")
groupingBy(..) 分组,根据函数式接口的返回值分组。 Collectors.groupingBy(item -> item.getAge() >= 18)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void collect() {

List<String> list = Arrays.asList("abc", "bc", "efg", null, "", "abc");

List<String> listResults = list.stream()
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());

Set<String> setResults = list.stream()
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toSet());

Map<String, String> mapResults = list.stream()
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toMap(e -> e, String::toUpperCase, (a, b) -> a));

String joinResult = list.stream()
.filter(StringUtils::isNotEmpty)
.collect(Collectors.joining(", "));

// [abc, bc, efg, abc]
System.out.println(listResults);
// [bc, abc, efg]
System.out.println(setResults);
// {bc=BC, abc=ABC, efg=EFG}
System.out.println(mapResults);
// abc, bc, efg, abc
System.out.println(joinResult);
}
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void toCollection() {

List<String> list = Arrays.asList("abc", "bc", "efg", null, "", "abc");

Set<String> setResults = list.stream()
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new));

// [abc, bc, efg]
System.out.println(setResults);
}

默认方法

Default methods : 允许将新功能添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

简单的说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。(🤣接口和抽象类都可以有实现方法了)

JDK8 以前,接口是个双刃剑,

  • 好处是面向抽象而不是面向具体编程,

  • 缺陷是当需要修改接口时候,需要修改全部实现该接口的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Vehicle {

/**
* 在接口中使用 default 就可以添加实现方法了
*/
default void print(){
System.out.println("Vehicle:默认方法调用!");
}

/**
* jdk8后,可以在接口中添加静态的实现方法(静态默认方法,🤣不能用default修饰)
*/
static void blowHorn(){
System.out.println("Vehicle:静态方法调用!");
}

}
1
2
3
4
5
6
7
public interface FourWheeler {

default void print(){
System.out.println("FourWheeler : 默认方法调用!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
public class Car implements Vehicle, FourWheeler {

@Override
public void print(){
// 使用 super 来调用指定接口的默认方法:
Vehicle.super.print();
FourWheeler.super.print();

Vehicle.blowHorn();
}

}
1
2
3
Vehicle:默认方法调用!
FourWheeler : 默认方法调用!
Vehicle:静态方法调用!

forEach 解析

forEach 是经典的默认方法,是 jdk8 集合框架可使用的遍历方法(实现于Iterable接口)。

1
2
3
4
5
6
7
8
9
10
@Test
public void test() {
List names = new ArrayList();
names.add("Google");
names.add("Runoob");

names.forEach(System.out::println);
// names.forEach(item -> System.out.println(item)); // 等同于以上的函数引用

}

JDK8 在 Iterable接口 中新增了默认方法 forEach

其会遍历集合,并调用 Consumer接口accept方法

1
2
3
4
5
6
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

Consumer接口 是 jdk8新增的 函数式接口

其提供的接口方法是:一个参数,没有返回值。

1
2
3
4
5
6
package java.util.function;

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

这里 System.out::printlnConsumer接口 有相同的参数和返回值。

故,这里集合会遍历执行 相应逻辑,即遍历打印。

1
names.forEach(System.out::println);

Optional API

Optional : 值容器, 可以简化空指针异常处理。

构造

1
2
3
4
5
6
7
8
9
10
@Test
public void construct() {


Optional<Integer> opt1 = Optional.of(1);

Optional<Integer> opt2 = Optional.ofNullable(null);
// Optional.ofNullable(null) 等效 Optional.empty()
Optional<Integer> opt3 = Optional.empty();
}

ifPresent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* ifPresent : 判断是否为空
* ifPresent(Consumer) : 判断是否为空,不为空则执行
*
* @see java.util.function.Consumer
*/
@Test
public void ifPresent() {

Optional<Integer> opt = Optional.of(1);

// true
System.out.println(opt.isPresent());

// true
opt.ifPresent(System.out::println);
}

orElse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* orElse : 为空则提供默认值
* orElseGet(Supplier) : 为空则提供默认值
* orElseThrow(Supplier) : 为空则抛异常
*
* @see java.util.function.Supplier
*/
@Test
public void orElse() throws Exception {

Optional<Integer> zeroOpt = Optional.of(0);
Optional<Integer> opt = Optional.ofNullable(null);

// 1
System.out.println(opt.orElse(1));
// 0
System.out.println(opt.orElseGet(zeroOpt::get));
// 0
System.out.println(zeroOpt.orElseThrow(Exception::new));
}

filter

1
2
3
4
5
6
7
8
9
10
11
/**
* filter(Predicate) : 过滤出符合条件的值,如不符合则返回Optional.empty()
*
* @see java.util.function.Predicate
*/
@Test
public void filter() {
Optional<Integer> opt = Optional.of(0);
// Optional.empty
System.out.println(opt.filter(e -> e != 0));
}

map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* map(Function) : 对Optional的值运算再返回
* flatMap(Function) : 对Optional的值运算再返回
*
* @see java.util.function.Function
*/
@Test
public void map() {
Optional<Integer> opt = Optional.of(1);

// Optional[100]
System.out.println(opt.map(e -> e * 100));

// Optional[100]
System.out.println(opt.flatMap(e -> Optional.of(e * 100)));
}

Date-Time API

JDK8 提供了 java.time包 来完善对日期和时间的处理,遵循 JSR310 规范, 其是建立在 Joda-Time 基础上的。

JDK8 之前,对于处理日期时间 API 相对糟糕,项目开发往往重度依赖第三方类库,如 Joda-Time

新的 API可以减轻工具类对第三方库的依赖,但不能替换旧的 API 。

JDK8之前的日期和时间处理 :

  • java.util.Date : 承载日期和时间信息。

  • java.util.Calendar : 对日期和时间进行操作。

  • java.text.DateFormat : 格式化和分析日期和时间字符串。

    下文皆以最常见的 SimpleDateFormat 为例说明。

槽点最多的 Java API :

  • Date , Calendar , SimpleDateFormat 所有属性都是可变的。

    1) 线程不安全 , 2) 重复计算时不能复用,只能新建对象。

  • Date , Calendar 操作不人性化,而且 Calendar API 是 IBM 捐的,所以设计不一致。

    1) 月份都是从 0 开始。2) Date 年是从 1900 开始的。

1
2
3
4
5
6
LocalDate nowDate = LocalDate.now();
System.out.println(nowDate); // 2019-05-14
System.out.println(nowDate.getYear() + "-" + nowDate.getMonthValue() + "-" + nowDate.getDayOfMonth()); // 2019-5-14

LocalDate customDate = LocalDate.of(9102, Month.JANUARY, 1);
System.out.println(customDate); // 9102-01-01

新增 API

这里这只列举部分常用的 API,其他会在功能性中补充。

涉及 说明
LocalDate LocalDate 是一个不可变的类,它表示日期,默认格式是 yyyy-MM-dd
LocalTime LocalTime 是一个不可变的类,它的实例代表可读格式的时间,默认格式是 hh:mm:ss.zzz
LocalDateTime LocalDateTime 是一个不可变的类,它表示一组日期-时间,默认格式是 yyyy-MM-ddTHH-mm-ss.zzz

LocalDate

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* LocalDate 是一个不可变的类,它表示日期,默认格式是 yyyy-MM-dd
*/
@Test
public void LocalDate() {

LocalDate nowDate = LocalDate.now();
System.out.println(nowDate); // 2019-05-14
System.out.println(nowDate.getYear() + "-" + nowDate.getMonthValue() + "-" + nowDate.getDayOfMonth()); // 2019-5-14

LocalDate customDate = LocalDate.of(9102, Month.JANUARY, 1);
System.out.println(customDate); // 9102-01-01
}

LocalTime

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* LocalTime 是一个不可变的类,它的实例代表可读格式的时间,默认格式是 hh:mm:ss.zzz
*/
@Test
public void LocalTime() {

LocalTime nowTime = LocalTime.now();
System.out.println(nowTime); // 11:14:47.718
System.out.println(nowTime.getHour() + ":" + nowTime.getMinute() + ":" + nowTime.getSecond()); // 11:18:32

LocalTime customTime = LocalTime.of(11, 2, 33);
System.out.println(customTime);
}

LocalDateTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* LocalDateTime 是一个不可变的类,它表示一组日期-时间,默认格式是 yyyy-MM-ddTHH-mm-ss.zzz
*/
@Test
public void LocalDateTime() {

LocalDateTime nowDateTime = LocalDateTime.now();
System.out.println(nowDateTime); // 2019-05-14T11:21:27.766
System.out.println(nowDateTime.getYear() + "-" + nowDateTime.getMonthValue() + "-" + nowDateTime.getDayOfMonth()); //2019-5-14

LocalDate customDate = LocalDate.of(9102, Month.JANUARY, 1);
LocalTime customTime = LocalTime.of(11, 2, 33);
LocalDateTime customDateTime = LocalDateTime.of(customDate, customTime);
System.out.println(customDateTime); // 9102-01-01T11:02:33

}

格式化和解析

JDK8 新增了 java.time.format.DateTimeFormatter 类,负责提供格式化信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void formatAndParse() {
// format
LocalDate date = LocalDate.now();
System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));// 20190514
System.out.println(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));// 2019-05-14

LocalTime time = LocalTime.now();
System.out.println(time.format(DateTimeFormatter.ofPattern("HH:mm:ss")));// 13:54:31

LocalDateTime dateTime = LocalDateTime.of(date, time);
System.out.println(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 2019-05-14 13:54:31

// parse
LocalDateTime dt = LocalDateTime.parse("2019-05-14 13:54:31", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dt); // 2019-05-14T13:54:31
}

处理和分析

新的 API 中内置了许多工具方法,可以分析和处理日期和时间。

注意 :这些类都是不可变动的,处理后是返回一个新的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Test
public void dealAndAnalyse(){

LocalDate today = LocalDate.now(); // 2019-05-14

System.out.println("当前是否是闰年: " + today.isLeapYear()); // 当前是否是闰年: false
System.out.println(today.isAfter(LocalDate.of(9102,1,1))); // false

// plus and minus : Day, Week, Month, Year
System.out.println("一天后: " + today.plusDays(1)); // 一天后: 2019-05-15
System.out.println(today); // 2019-05-15
System.out.println("一天前: " + today.minusDays(1)); // 一天前: 2019-05-13

LocalDate dt = LocalDate.parse("2020-03-31", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
System.out.println(dt.minusMonths(1));// 2020-02-29
System.out.println(dt.minusYears(1).minusMonths(1));// 2019-02-28

// lastDayOfMonth, lastDayOfYear
LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
System.out.println(lastDayOfYear); // 2019-12-31

// 时间间隔
Period period = today.until(lastDayOfYear);
System.out.println(period.getDays()); // 17
System.out.println(period.getMonths()); // 7
System.out.println(period.getYears()); // 0

}

支持旧 API

JDK8 新增了一个 java.time.Instant 类,借助它可以完成 旧API新API 的相互转换。

1
2
3
4
5
6
7
8
9
10
@Test
public void old2new(){
// Date,Calendar -> Instant -> LocalDateTime
Instant timestamp1 = new Date().toInstant();
Instant ts1 = Calendar.getInstance().toInstant();

LocalDateTime dateTime1 = LocalDateTime.ofInstant(timestamp1, ZoneId.systemDefault());
LocalDate date1 = dateTime1.toLocalDate();
LocalTime time1 = dateTime1.toLocalTime();
}
1
2
3
4
5
6
7
8
9
@Test
public void new2old(){
// LocalDateTime,LocalDate -> Instant -> Date
Instant timestamp2 = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
Instant ts2 = LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();

Date date2 = Date.from(timestamp2);
Date d2 = Date.from(ts2);
}