JDK 1.8 新特性

Lambda

简介

Lambda 是一个 匿名函数,我们可以把 Lambda 表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

从匿名类到 Lambda 的转换示例1

在多线程开发中需要手动 new 一个匿名的实现了 Runnable 接口的类,并执行 start() 方法启动一个新的线程,

注意:下面代码只是方法调用,并不是启动新的线程。仅用 Runnable 接口示例如果使用 Lambda 表达式该如何编写代码。

示例一:

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

Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("woodwhales.github.io");
}
};

r1.run();
}

使用 Lambda 表达式会让代码显得更简洁,其程序运行的效果还是一样的:

1
2
3
4
5
@Test
public void test2() {
Runnable r2 = () -> System.out.println("woodwhales.github.io");
r2.run();
}

从匿名类到 Lambda 的转换示例2

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

Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};

int compare1 = com1.compare(12,21);
System.out.println(compare1);
}

Lambda 表达式简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test4() {
//Lambda表达式的写法
Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2);

int compare2 = com2.compare(32,21);
System.out.println(compare2);

//更为简洁的写法:方法引用
Comparator<Integer> com3 = Integer :: compare;

int compare3 = com3.compare(32,21);
System.out.println(compare3);
}

语法

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为-> , 该操作符被称为 Lambda 操作符或 箭 头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的 参 数 列表

右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

语法格式一

无参,无返回值

1
Runnable r2 = () -> {System.out.println("woodwhales.github.io");};

语法格式二

Lambda 需要一个参数,但是没有返回值。

1
2
3
Runnable runnable = () -> {
System.out.println("woodwhales.github.io");
};

语法格式三

数据类型可以省略 ,因为可由编译器推断得出,称为类型推断

1
2
3
Consumer<String> consumer1 = (String str) -> {
System.out.println(str);
};

语法格式四

Lambda 若只需要一个参数时,参数的小括号可以省略

1
2
3
Consumer<String> consumer3 = str -> {
System.out.println(str);
};

语法格式五

Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

1
2
3
4
Comparator<Integer> comparator1 = (x, y) -> {
System.out.println("实现函数式接口");
return Integer.compare(x, y);
};

语法格式六

当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

1
Comparator<Integer> comparator2 = (x, y) -> Integer.compare(x, y);

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断

函数式接口(Functional)

概念

如果一个接口中只声明了一个抽象方法,则此接口就称为函数式接口。

Lambda 表达式可以创建该接口的对象。

我们可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

JDK 1.8 API 文档上会显示带有@FunctionalInterface注解:

自定义函数式接口

我们可以在一个接口上使用@FunctionalInterface 注解,自己创建一个接口,并仅仅提供一个方法,使用@FunctionalInterface注解:

1
2
3
4
5
6
7
8
9
10
/**
* 函数式接口注解,用来表示当前接口有且只有一个方法
* 自定义函数式接口
*/
@FunctionalInterface
public interface MyFunctionalInterface {

void sayHello();

}

注意:使用了函数式接口注解这个接口,这个接口有超过一个抽象方法,会报编译异常。

使用 Lambda 表达式创建上述自定义的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void say(MyFunctionalInterface myFunctionalInterface) {
myFunctionalInterface.sayHello();
}

public static void main(String[] args) {
/**
* 传统写法
*/
say(new MyFunctionalInterface() {
@Override
public void sayHello() {
System.out.println("hello world");
}
});

// 调用say方法,方法的参数是一个函数式接口,所以可以Lambda表达式
say(() -> {
System.out.println("使用Lambda表达式重写接口中的抽象方法");
});

// 简化Lambda表达式:当实现类中只有一行代码的时候,可以简写
say(() -> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}

java.util.function包下定义了Java 8 的丰富的函数式接口:

序号 接口名及描述
1 BiConsumer代表了一个接受两个输入参数的操作,并且不返回任何结果。
2 BiFunction代表了一个接受两个输入参数的方法,并且返回一个结果。
3 BinaryOperator代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。
4 BiPredicate代表了一个两个参数的boolean值方法。
5 BooleanSupplier代表了boolean值结果的提供方。
6 Consumer代表了接受一个输入参数并且无返回的操作。
7 DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction代表接受一个double值参数的方法,并且返回结果。
10 DoublePredicate代表一个拥有double值参数的boolean值方法。
11 DoubleSupplier代表一个double值结构的提供方。
12 DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction接受一个double类型输入,返回一个long类型结果。
14 DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。
15 Function接受一个输入参数,返回一个结果。
16 IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer接受一个int类型的输入参数,无返回值 。
18 IntFunction接受一个int类型输入参数,返回一个结果 。
19 IntPredicate:接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier无参数,返回一个int类型结果。
21 IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer接受一个long类型的输入参数,无返回值。
26 LongFunction接受一个long类型输入参数,返回一个结果。
27 LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier无参数,返回一个结果long类型的值。
29 LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate接受一个输入参数,返回一个布尔值结果。
36 Supplier无参数,返回一个结果。
37 ToDoubleBiFunction接受两个输入参数,返回一个double类型结果。
38 ToDoubleFunction接受一个输入参数,返回一个double类型结果。
39 ToIntBiFunction接受两个输入参数,返回一个int类型结果。
40 ToIntFunction接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction接受两个输入参数,返回一个long类型结果。
42 ToLongFunction接受一个输入参数,返回一个long类型结果。
43 UnaryOperator接受一个参数为类型T,返回值类型也为T。

对于 JDK1.8 中提供的这么多函数式接口,开发中常用的函数式接口有以下几个 Predicate,Consumer,Function,Supplier:

内置四大核心函数式接口

函数式接口 参数类型 返回类型 用途
Consumer 消费型接口 T void 对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier 供给型接口 T 返回类型为T的对象,包含方法:T get()
Function<T, R> 函数型接口 T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)

消费型接口

Consumer void accept(T t)

java.util.function.Consumer接口定义了一个名叫 accept 的抽象方法,它接受泛型T,没有返回值(void)。如果需要访问类型 T 的对象,并对其执行某些操作,可以使用这个接口,通常称为消费型接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void showPrice(double money, Consumer<Double> consumer){
consumer.accept(money);
}

@Test
public void test5(){

showPrice(2, new Consumer<Double>() {
@Override
public void accept(Double money) {
System.out.println("今天白菜的价格为:" + money);
}
});

System.out.println("********************");

showPrice(400, money -> System.out.println("明天黄金的价格为:" + money));
}

供给型接口

Supplier T get()

java.util.function.Supplier接口定义了一个get的抽象方法,它没有参数,返回一个泛型T的对象,这类似于一个工厂方法,通常称为供给型接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Properties readFile(String fileName) {
Supplier<Properties> supplier = () -> {
try {
InputStream is = TestCase04.class.getClassLoader().getResourceAsStream(fileName);
Properties prop = new Properties();
prop.load(is);
return prop;
} catch (IOException e) {
e.printStackTrace();
return null;
}
};

return supplier.get();
}

函数型接口

Function<T,R> R apply(T t)

java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果需要定义一个Lambda,将输入的信息映射到输出,可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度),通常称为功能型接口。

1
2
3
4
5
// 实现用户密码 Base64加密操作
Function<String,String> encoded = (password)-> Base64.getEncoder().encodeToString(password.getBytes());

// 输出加密后的字符串
System.out.println(encoded.apply("123456"));

断定型接口

Predicate boolean test(T t)

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
public List<String> filterString(List<String> list, Predicate<String> predicate){

ArrayList<String> filterList = new ArrayList<>();

for(String s : list){
if(predicate.test(s)){
filterList.add(s);
}
}

return filterList;

}

@Test
public void test6(){
List<String> list = Arrays.asList("北京","南京","天津","东京","普京");

List<String> filterStrs1 = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});

System.out.println(filterStrs1);

System.out.println("********************");

List<String> filterStrs2 = filterString(list,s -> s.contains("京"));
System.out.println(filterStrs2);
}

其他函数式接口

函数式接口 参数类型 返回类型 用途
BiFunction<T, U, R> T, U R 对类型为 T, U 参数应用操作,返回 R 类型的结果。
包含方法为:R apply(T t, U u)
UnaryOperator(Function) T T 对类型为T的对象进行一元运算,并返回T类型的结果。
包含方法为:T apply(T t)
BinaryOperator(BiFunction 子接口) T, T T 对类型为T的对象进行二元运算,并返回T类型的结果。
包含方法为: T apply(T t1, T t2)
BiConsumer<T, U> T, U void 对类型为T, U 参数应用操作。
包含方法为: void accept(T t, U u)
BiPredicate<T,U> T, U boolean 包含方法为: boolean test(T t, U u)
ToIntFunction
ToLongFunction
ToDoubleFunction
T int
long
double
分别计算int、long、double值的函数
IntFunction
LongFunction
DoubleFunction
int
long
double
R 参数分别为int、long、double 类型的函数

方法引用

当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用。方法引用,本质上就是 Lambda 表达式,而 Lambda 表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例。

方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个语法糖。

使用格式: 类(或对象)::方法名

具体分为如下的三种情况:

  • 情况1:对象 :: 实例方法

  • 情况2:类 :: 静态方法

  • 情况3:类 :: 非静态方法

情况1 和情况3 的方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同。

情况1、对象 :: 非静态方法

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
// Consumer 中的 void accept(T t)
// PrintStream 中的 void println(T t)
@Test
public void test7() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");

System.out.println("*******************");

Consumer<String> con2 = System.out::println;
con2.accept("beijing");
}

// Supplier 中的 T get()
// Employee 中的 String getName()
@Test
public void test8() {
Person emp = new Person(1001,"Tom", 23);

Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());

System.out.println("*******************");

Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}

情况2、类 :: 静态方法

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
// Comparator 中的 int compare(T t1,T t2)
// Integer 中的 int compare(T t1,T t2)
@Test
public void test9() {
Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12,21));

System.out.println("*******************");

Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(12,3));
}

// Function 中的 R apply(T t)
// Math 中的 Long round(Double d)
@Test
public void test10() {
Function<Double,Long> func = new Function<Double, Long>() {
@Override
public Long apply(Double d) {
return Math.round(d);
}
};

System.out.println("*******************");

Function<Double,Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(12.3));

System.out.println("*******************");

Function<Double,Long> func2 = Math::round;
System.out.println(func2.apply(12.6));
}

情况3、类 :: 实例方法

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
// Comparator 中的 int comapre(T t1,T t2)
// String 中的 int t1.compareTo(t2)
@Test
public void test11() {
Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc","abd"));

System.out.println("*******************");

Comparator<String> com2 = String :: compareTo;
System.out.println(com2.compare("abd","abm"));
}

// BiPredicate 中的 boolean test(T t1, T t2);
// String 中的 boolean t1.equals(t2)
@Test
public void test12() {
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc","abc"));

System.out.println("*******************");

BiPredicate<String,String> pre2 = String :: equals;
System.out.println(pre2.test("abc","abd"));
}

// Function 中的 R apply(T t)
// Employee 中的 String getName()
@Test
public void test13() {
Employee employee = new Employee(1001, "Jerry", 23);

Function<Employee,String> func1 = e -> e.getName();
System.out.println(func1.apply(employee));

System.out.println("*******************");

Function<Employee,String> func2 = Employee::getName;
System.out.println(func2.apply(employee));

}

构造器引用

构造器引用与函数式接口相结合,自动与函数式接口中方法兼容。格式为:ClassName::new

可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致,且方法的返回值即为构造器对应类的对象。

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
// Supplier 中的 T get()
// Employee 的空参构造器:Employee()
@Test
public void test14(){

Supplier<Employee> sup = new Supplier<Employee>() {
@Override
public Employee get() {
return new Employee();
}
};

System.out.println("*******************");

Supplier<Employee> sup1 = () -> new Employee();
System.out.println(sup1.get());

System.out.println("*******************");

Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
}

// Function 中的 R apply(T t)
@Test
public void test15(){
Function<Integer,Employee> func1 = id -> new Employee(id);
Employee employee = func1.apply(1001);
System.out.println(employee);

System.out.println("*******************");

Function<Integer,Employee> func2 = Employee::new;
Employee employee1 = func2.apply(1002);
System.out.println(employee1);
}

// BiFunction 中的 R apply(T t,U u)
@Test
public void test16(){
BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
System.out.println(func1.apply(1001,"Tom"));

System.out.println("*******************");

BiFunction<Integer,String,Employee> func2 = Employee::new;
System.out.println(func2.apply(1002,"King"));

}

数组引用

数组引用和构造器引用类似,格式为:type[]::new

1
2
3
4
5
6
7
8
9
10
11
12
13
// Function 中的 R apply(T t)
@Test
public void test17(){
Function<Integer,String[]> func1 = length -> new String[length];
String[] arr1 = func1.apply(5);
System.out.println(Arrays.toString(arr1));

System.out.println("*******************");

Function<Integer,String[]> func2 = String[]::new;
String[] arr2 = func2.apply(10);
System.out.println(Arrays.toString(arr2));
}

Stream API

官方文档: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到 Java 中。这是目前为止对 Java 类库最好的补充,因为 Stream API 可以极大提供 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

实际开发中,项目中多数数据源都来自于 Mysql,Oracle 等。但现在数据源可以更多了,有 MongDB,Redis 等,而这些 NoSQL 的数据就需要 Java 层面去处理。

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

Stream 到底是什么

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤

Stream API 可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

步骤1:创建 Stream

一个数据源(如:集合、数组),获取一个流

步骤2:中间操作

一个中间操作链,对数据源的数据进行处理

步骤3:终止操作(终端操作)

一旦执行终止操作,就执行中间操作链,并产生结果。终止操作结束之后,当前被终止的流中数据不会再被使用。

1
2
3
+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+

中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新 stream。

结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以 pipeline 的方式执行,这样可以减少迭代次数。计算完成之后 stream 就会失效。

虽然大部分情况下 stream 是容器调用Collection.stream()方法得到的,但 stream 和 collections 有以下不同:

  • 无存储。stream 不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java 容器或 I/O channel等。

  • 为函数式编程而生。对 stream 的任何修改都不会修改背后的数据源,比如对 stream 执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新 stream。

  • 惰式执行。stream 上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。

  • 可消费性。stream 只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

流的使用及分类

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining:中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。
  • 内部迭代:以前对集合遍历都是通过 Iterator 或者 For-Each 的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream 提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

流的使用

流操作的分类

流操作分类为:

  • 中间操作(Intermediate)

    无状态操作:filter / map / peek 等,指元素的处理不受之前元素的影响。

    有状态操作:distinct / sorted / limit 等,指该操作只有拿到所有元素之后才能继续下去。

  • 终端操作(Terminal)

    非短路操作:forEach / collect / count 等,指遇到某些符合条件的元素就可以得到最终结果。

    短路操作:anyMatch / findFirst / findAny 等,指必须处理所有元素才能得到最终结果。

Stream 的创建

方式1:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

顺序流:default Stream stream()
并行流:default Stream parallelStream()

1
2
3
4
5
6
7
8
9
10
Test
public void test18(){
List<Employee> employees = EmployeeData.getEmployees();

// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();

// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}

方式2:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

static Stream stream(T[] array)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test19(){
int[] arr = new int[]{1, 2, 3, 4, 5, 6};

// 调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);

Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};

Stream<Employee> stream1 = Arrays.stream(arr1);
}

方式3:通过Stream 的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数:

public static Stream of(T… values)

1
2
3
4
5
6
@Test
public void test20(){

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

}

方式4:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流:

迭代:public static Stream iterate(final T seed, final UnaryOperator f)

生成:public static Stream generate(Supplier s)

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

// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
// 遍历前10个偶数,如果不用 limit() 限制,那么会无限计算下去,直到栈溢出
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);

// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
// 生成 10 个随机数,如果不用 limit() 限制,那么会无限生成随机数,直到栈溢出
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}

Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为惰性求值(中间操作只是对操作进行了记录,只有结束操作才会触发实际的计算)。

类型1:筛选与切片

方法 描述
filter(Predicate p) 过滤,接收 Lambda ,并从流中排除某些元素
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

代码示例:

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
@Test
public void test22(){
List<Employee> list = EmployeeData.getEmployees();


Stream<Employee> stream = list.stream();

// filter(Predicate p)
// 实战:查询员工表中薪资大于5000的员工信息
stream.filter(e -> e.getSalary() > 5000).forEach(System.out::println);

System.out.println();

// limit(n)
// 实战:使其元素不超过给定数量。
list.stream().limit(3).forEach(System.out::println);

System.out.println();

// skip(n)
// 实战:跳过前三个员工,遍历之后的
list.stream().skip(3).forEach(System.out::println);

System.out.println();

// distinct()
// 实战:去重
list.add(new Employee(1020,"Tom",40,8000));
list.add(new Employee(1020,"Tom",41,8000));
list.add(new Employee(1020,"Tom",40,8000));
list.add(new Employee(1020,"Tom",40,8000));
list.add(new Employee(1020,"Tom",40,8000));
list.stream().distinct().forEach(System.out::println);
}

类型2:映射

方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

代码示例:

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
@Test
public void test23(){
// map(Function f)
// 实战:将数组中的元素都转换成大写
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);

// map + filter
// 实战:获取员工姓名长度大于3的员工的姓名
List<Employee> employees = EmployeeData.getEmployees();
Stream<String> namesStream = employees.stream().map(Employee::getName);
namesStream.filter(name -> name.length() > 3).forEach(System.out::println);

System.out.println();

// map 每个集合都是一个流
List<Integer> aList = Arrays.asList(1, 2, 3);
List<Integer> bList = Arrays.asList(4, 5);
Stream<List<Integer>> stream = Stream.of(aList, bList);

// flatMap 所有集合合并成一个流
List<Integer> cList = Arrays.asList(6, 7, 8);
List<Integer> dList = Arrays.asList(9, 10);
Stream<Integer> integerStream = Stream.of(cList, dList).flatMap(integers -> integers.stream());
}

类型3:排序

方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test24(){
// sorted() 自然排序
List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
list.stream().sorted().forEach(System.out::println);

// 抛异常,原因:Employee没有实现Comparable接口
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out::println);

// sorted(Comparator com) 定制排序

List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted( (e1,e2) -> {

int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}

}).forEach(System.out::println);
}

Stream 的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

注意:流进行了终止操作后,不能再次使用。

类型1:匹配与查找

方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,stream 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
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
@Test
public void test25(){
List<Employee> employees = EmployeeData.getEmployees();

// allMatch(Predicate p) 检查是否匹配所有元素。
// 实战:是否所有的员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch);

// anyMatch(Predicate p) 检查是否至少匹配一个元素。
// 实战:是否存在员工的工资大于 10000
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(anyMatch);

// noneMatch(Predicate p) 检查是否没有匹配的元素。
// 实战:是否存在员工姓“K”
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("K"));
System.out.println(noneMatch);

// findFirst 返回第一个元素
// 实战:找到第一个元素
Optional<Employee> employee = employees.stream().findFirst();
System.out.println(employee);

// findAny 返回当前流中的任意元素
// 找任意一个元素
Optional<Employee> employee1 = employees.parallelStream().findAny();
System.out.println(employee1);
}

@Test
public void test26(){
List<Employee> employees = EmployeeData.getEmployees();

// count 返回流中元素的总个数
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);

// max(Comparator c) 返回流中最大值
// 实战:返回最高的工资
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> maxSalary = salaryStream.max(Double::compare);
System.out.println(maxSalary);

// min(Comparator c) 返回流中最小值
// 实战:返回最低工资的员工
Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(employee);
System.out.println();

// forEach(Consumer c)——内部迭代
employees.stream().forEach(System.out::println);

//使用集合的遍历操作
employees.forEach(System.out::println);
}

类型2:规约

方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test27(){
// reduce(T identity, BinaryOperator)
// 实战:求所有元素的总和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);

// reduce(BinaryOperator)
// 实战:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
// Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
System.out.println(sumMoney.get());
}

类型3:收集

方法 描述
collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test28(){
// collect(Collector c)
// 实战:查找工资大于5000的员工,结果返回为一个List
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toList());

employeeList.forEach(System.out::println);

System.out.println();

// 实战:查找工资大于5000的员工,结果返回为一个Set
Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toSet());

employeeSet.forEach(System.out::println);
}

Collectors

另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

官方文档:https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html

工厂方法 返回类型 用途 示例
toList List 把流中所有项目收集到一个 List List projects = projectStream.collect(toList());
toSet Set 把流中所有项目收集到一个 Set,删除重复项 Set projects = projectStream.collect(toSet());
toCollection Collection 把流中所有项目收集到给定的供应源创建的集合 Collection projects = projectStream.collect(toCollection(), ArrayList::new);
counting Long 计算流中元素的个数 long howManyProjects = projectStream.collect(counting());
summingInt Integer 对流中项目的一个整数属性求和 int totalStars = projectStream.collect(summingInt(Project::getStars));
averagingInt Double 计算流中项目 Integer 属性的平均值 double avgStars = projectStream.collect(averagingInt(Project::getStars));
summarizingInt IntSummaryStatistics 收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值 IntSummaryStatistics projectStatistics = projectStream.collect(summarizingInt(Project::getStars));
joining String 连接对流中每个项目调用 toString 方法所生成的字符串 String shortProject = projectStream.map(Project::getName).collect(joining(", "));
maxBy Optional 按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty() Optional fattest = projectStream.collect(maxBy(comparingInt(Project::getStars)));
minBy Optional 按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty() Optional fattest = projectStream.collect(minBy(comparingInt(Project::getStars)));
reducing 归约操作产生的类型 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 int totalStars = projectStream.collect(reducing(0, Project::getStars, Integer::sum));
collectingAndThen 转换函数返回的类型 包含另一个收集器,对其结果应用转换函数 int howManyProjects = projectStream.collect(collectingAndThen(toList(), List::size));
groupingBy Map> 根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键 Map> projectByLanguage = projectStream.collect(groupingBy(Project::getLanguage));
partitioningBy Map> 根据对流中每个项目应用断言的结果来对项目进行分区 Map> vegetarianDishes = projectStream.collect(partitioningBy(Project::isVegetarian));

Project 类:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import lombok.Builder;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
@Builder
public class Project {

/**
* 项目名称
*/
private String name;

/**
* 编程语言
*/
private String language;

/**
* star 数
*/
private Integer stars;

/**
* 描述
*/
private String description;

/**
* 作者
*/
private String author;

/**
* fork数
*/
private Integer forks;

public static List<Project> buildData(){
List<Project> data = new ArrayList<>();

data.add(Project.builder()
.name("Jack")
.language("javascript")
.author("AAA")
.stars(2600)
.forks(2300)
.description("Best beautiful java blog, worth a try")
.build());

data.add(Project.builder()
.name("Vue.js")
.language("js")
.author("BBB")
.stars(83000)
.forks(10322)
.description("A progressive, incrementally-adoptable JavaScript framework for building UI on the web.")
.build());

data.add(Project.builder()
.name("Flask")
.language("python")
.author("CCC")
.stars(10500)
.forks(3000)
.description("The Python micro framework for building web applications")
.build());

data.add(Project.builder()
.name("Elves")
.language("java")
.author("DDD")
.stars(200)
.forks(100)
.description("Spider")
.build());
data.add(Project.builder()
.name("Tom")
.language("java")
.author("EEE")
.stars(3500)
.forks(2000)
.description("Lightning fast and elegant mvc framework for Java8")
.build());
return data;
}

}

Optional

官方文档:https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。以前,为了解决空指针异常,Google 公司著名的 Guava 项目引入了 Optional 类,Guava 通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava 的启发,Optional 类已经成为 Java 8 类库的一部分。

Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

Optional类的 Javadoc 描述如下:这是一个可以为 null 的容器对象。如果值存在则isPresent()方法会返回 true,调用get()方法会返回该对象。

创建Optional 类对象的方法

Optional.of(T t) : 创建一个 Optional 实例,t必须非空

Optional.empty() : 创建一个空的 Optional 实例

Optional.ofNullable(T t) :t可以为null

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test29(){
Girl girl = new Girl();
// girl = null;
// of(T t): t 必须为非空,否则抛出空指针异常
Optional<Girl> optionalGirl = Optional.of(girl);
}

@Test
public void test30(){
Girl girl = new Girl();
girl = null;
// 不会抛出空指针异常
Optional<Girl> optionalGirl = Optional.of(girl);
}

判断Optional 容器中是否包含对象

boolean isPresent() : 判断是否包含对象

void ifPresent(Consumer<? super T> consumer) :如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它。

代码示例:

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

List<String> listA = new ArrayList<>();
// 元素不为null
boolean listAIsPresent = Optional.ofNullable(listA).isPresent(); // true
System.out.println(listAIsPresent);

List<String> listB = new ArrayList<>();
listB = null;
// 元素为null
boolean listBIsPresent = Optional.ofNullable(listB).isPresent(); // false
System.out.println(listBIsPresent);
}

获取 Optional 容器的对象

T get(): 如果调用对象包含值,返回该值,否则抛异常

T orElse(T other) :如果有值则将其返回,否则返回指定的other对象

T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。

T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

代码示例:

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 test32() {

List<String> listA = new ArrayList<>();
listA.add("AA");
// listA 不为null,执行了 say() 中代码,但是仅仅是执行了,没有返回结果
List<String> listAResult = Optional.ofNullable(listA).orElse(say());
System.out.println(listAResult);

System.out.println("----------");

List<String> listB = null;
// listA 为null,执行了 say() 中代码返回了结果
List<String> listBResult = Optional.ofNullable(listB).orElse(say());
System.out.println(listBResult);
}

private List<String> say() {
System.out.println("orElse 执行了");
List<String> list = new ArrayList<>();
list.add("orElse 创建的集合");
return list;
}

========== 执行结果 ==========
orElse 执行了
[]
----------
orElse 执行了
[orElse 创建的集合]

从执行输出的结果可以看出:当 Optional.ofNullable() 接收到的对象:

元素是 null,返回 orElse()中的结果是符合 API 描述的。

元素即不为 null,orElse() 也会被执行一次,并且仅仅是执行一下,并不会返回该方法中的结果。这看起来有点浪费资源,多此一举。

因此可以使用 orElseGet()

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
@Test
public void test33() {

List<String> listA = new ArrayList<>();
listA.add("AA");
List<String> listAResult = Optional.ofNullable(listA).orElseGet(() -> say());
System.out.println(listAResult);

System.out.println("----------");

List<String> listB = null;
List<String> listBResult = Optional.ofNullable(listB).orElseGet(() -> say());
System.out.println(listBResult);
}

private List<String> say() {
System.out.println("orElse 执行了");
List<String> list = new ArrayList<>();
list.add("orElse 创建的集合");
return list;
}

========== 执行结果 ==========
[AA]
----------
orElse 执行了
[orElse 创建的集合]

Optional.get()方法获取 Optional 容器中的值,执行下面代码会发现,容器中的值是 null,get() 的时候会抛出空指针异常:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test34() {
Optional<String> AA = Optional.ofNullable("AA");

String aa = AA.get();
System.out.println(aa); // 正常获取到了值

Optional<String> BB = Optional.ofNullable(null);

String bb = BB.get(); // 抛出异常: java.util.NoSuchElementException: No value present
System.out.println(bb);
}

Optional.ifPresent()方法会在容器中的元素为 null 的时候执行这个方法中的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test35() {
Optional<String> AA = Optional.ofNullable("AA");

AA.ifPresent((x) -> System.out.println(x));

Optional<String> BB = Optional.ofNullable(null);

BB.ifPresent((x) -> System.out.println(x));
}

========== 执行结果 ==========
AA

从上述结果可以看出,当容器中的元素不为空的时候,执行ifPresent()方法中的程序,并且这个方法不返回任何值,仅仅是”消费掉”元素。

时间日期API

第三次引入的 API 是成功的,并且Java 8中引入的 java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。

Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)
和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。

LocalDate/LocalTime/LocalDateTime

LocalDateLocalTimeLocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601 日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

  • LocalDate 代表 IOS 格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。 只对年月日做出处理。
  • LocalTime 表示一个时间,而不是日期。 只对时分秒纳秒做出处理。
  • LocalDateTime 是用来表示日期和时间的,这是一个最常用的类之一。 同时可以处理年月日和时分秒。

三种共同特点:

  • 相比于 Date 和 Calendar,他们是线程安全的;
  • 它们是不可变的日期时间对象;

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test36() {
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();

System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
}

========== 执行结果 ==========
2019-11-03
13:01:32.045
2019-11-03T13:01:32.045

LocalDateTime 常用 API

方法 描述
now()
now(Zoneld zone)
静态方法,根据当前时间创建对象 ; 指定时区的对象
of() 静态方法,根据指定日期,时间创建对象
getDayOfMonth() 获得月份天数(1-31)
getDayOfYear() 获取年份天数(1-366)
getDayOfWeek() 获得星期几
getYear() 获得年份
getMonth()
getMonthValue()
获得月份(返回枚举值如:January) ; 返回数字(1-12)
getHour()
getMinute()
getSecond()
获得当前对象的时,分,秒
withDayOfMonth()
withDayOfYear()
withMonth()
withYear()
将月份天数;年份天数;月份;年份修改为指定的值并且返回新的对象,因为LocalDate等是不变性的
plusDays()
plusWeeks()
plusMonths()
plusYears()
plusHours()
向当前对象添加几天、几周、几个月、几年,几小时
minusDays()
minusWeeks()
minusMonths()
minusYears()
minusHours()
从当前对象减去几月、几周、几个月、几年、几小时
isLeapYear() 判断是否为闰年
isBefore
isEqual
isAfter
检查日期是否在指定日期前面,相等,后面

上述 API 在 LocalDate 、LocalTime 和 LocalDateTime 三者几乎是通用的。

瞬时(Instant)

Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。

在处理时间和日期的时候,我们通常会想到年,月,日,时,分,秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。

java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。

(1 ns = 10 -9 s) 1秒 = 1000毫秒 =10^6微秒=10^9纳秒

常用 API:

方法 描述
now() 静态方法,返回默认的UTC时区的Instant类的对象
atOffset(ZoneOffset offset) 将此瞬间与偏移组合起来创建一个OffsetDateTime
toEpochMilli() 返回1970-01-01 00:00:00到当前的毫秒数,即时间戳
ofEpochMilli(long epochMilli) 静态方法,返回在1970-01-01 00:00:00基础加上指定毫秒数之后的Instant类的对象
ofEpochSecond(long epochSecond) 静态方法,返回在1970-01-01 00:00:00基础加上指定秒数之后的Instant类的对象

时间戳是指格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒( 北京时间 1970 年 01 月 01日 08 时 00 分00 秒) 起至现在的总秒数。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test37() {
Instant instant = Instant.now();
// 默认格林威治时间
System.out.println(instant);

// 指定时区偏移8小时,输出北京时间
LocalDateTime localDateTime = instant.atOffset(ZoneOffset.ofHours(8)).toLocalDateTime();
System.out.println(localDateTime);
}

========== 执行结果 ==========
2019-11-03T05:54:59.319Z
2019-11-03T13:54:59.319

根据当前系统的时区值,获取当前时间:

1
2
3
4
5
6
7
8
@Test
public void test38() {
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of(ZoneId.systemDefault().getId()));
System.out.println(localDateTime);
}

========== 执行结果 ==========
2019-11-03T14:05:08.131

时期格式化(DateTimeFormatter)

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)

自定义的格式。如:ofPattern(“yyyy-MM-dd HH:mm:ss”)

常用API

方法 描述
ofPattern(String pattern) 静态方法 , 返 回一 个指定字符串格式的 DateTimeFormatter
format(TemporalAccessor t) 格式化一个日期、时间,返回字符串
parse(CharSequence text) 将指定格式的字符序列解析为一个日期、时间

最常用的方式使用ofPattern(String pattern)创建自定义的格式化工具。

时间转时间字符串,代码示例:

1
2
3
4
5
6
7
8
9
10
@Test
public void test39() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 将当前时间格式化
String now = formatter.format(LocalDateTime.now());
System.out.println(now);
}

========== 执行结果 ==========
2019-11-03 14:17:20

时间字符串转时间,代码示例:

1
2
3
4
5
6
7
8
9
@Test
public void test40() {
TemporalAccessor temporalAccessor = formatter.parse("2019-11-03 14:14:40");
LocalDateTime localDateTime = LocalDateTime.from(temporalAccessor);
System.out.println(localDateTime);
}

========== 执行结果 ==========
2019-11-03T14:14:40

新日期与传统日期的转换

To 遗留类 From 遗留类
java.time.Instant 与 java.util.Date Date.from(instant) date.toInstant()
java.time.Instant与java.sql.Timestamp Timestamp.from(instant) timestamp.toInstant()
java.time.Zoned DateTime与java.util.GregorianCalendar GregorianCalendar.from(zonedDateTime) cal.toZonedDateTime()
java.time.LocalDate与java.sql.Time Date.valueOf(localDate) date.toLocalDate()
java.time.LocalTime与java.sql.Time Date.valueOf(localDate) date.toLocalTime()
java.time.LocalDateTime与java.sql.Timestamp Timestamp.valueOf(localDateTime) timestamp.toLocalDateTime()
java.time.ZoneId与java.util.TimeZone Timezone.getTimeZone(id) timeZone.toZoneId()
java.time.format.DateTimeFormatter 与 java.text.DateFormat formatter.toFormat()

JDBC 与 新API 结合

如果想要在 JDBC 中,使用 Java8 的日期 LocalDate、LocalDateTime,则必须要求数据库驱动的版本不能低于 4.2

最新JDBC映射将把数据库的日期类型和Java 8的新类型关联起来:

SQL Java
date LocalDate
time LocalTime
timestamp LocalDateTime

参考资料

跟上Java8 - 带你实战Java8

Java 8 中的 Streams API 详解

Java8 新特性教程

Java8 新特性 Stream 流教程

Java 8 教程汇总

深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

《跟上 Java 8》 github 代码

updated updated 2024-01-05 2024-01-05
本文结束感谢阅读

本文标题:JDK 1.8 新特性

本文作者:

微信公号:木鲸鱼 | woodwhales

原始链接:https://woodwhales.cn/2019/11/03/050/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%