Java8新特性

Java8新特性

Java8新特性简介

Lambda表达式

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

Lambda表达式使用前后的对比

举例一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test1(){

Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};

r1.run();

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

Runnable r2 = () -> System.out.println("我爱北京故宫");

r2.run();
}

举例二:

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

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);

System.out.println("***********************");
//Lambda表达式的写法
Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2);

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


System.out.println("***********************");
//方法引用
Comparator<Integer> com3 = Integer :: compare;

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

Lambda表达式的基本语法

举例

(o1,o2) -> Integer.compare(o1,o2);

格式

1
2
3
-> :lambda操作符 或 箭头操作符
->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
->右边:lambda体 (其实就是重写的抽象方法的方法体)

如何使用

分为六种情况

img

img

总结六种情况

->左边:lambda形参列表的参数类型可以省略(类型推断)

​ 如果lambda形参列表只一个参数,其一对()也可以省略

->右边:lambda体应该使用一对{}包裹

​ 如果lambda体只一条执行语句(可能是return语句),省略这一对{}和return关键字

注:

image-20200218113759423

函数式接口

什么是函数式接口

Functinal接口

只包含一个抽象方法的接口,称为函数式接口

可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)

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

函数式接口的使用说明

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

我们可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口

image-20200218121508728

Lambda表达式的本质:作为函数式接口的实例

以前使用匿名实现类表示的现在都可以用lambda表达式来实现

在java.util.function包下定义了java8的丰富的函数式接口

自定义函数式接口

image-20200218123109498

Java8中关于Lambda表达式提供的4个基本的函数式接口

img

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
public class LambdaTest2 {
@Test
public void test01(){
happyTime(400, new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println("花了"+integer);
}
});
System.out.println("********");
happyTime(400,(integer)->{
System.out.println("花了"+integer);
});
}

public void happyTime(Integer money, Consumer<Integer> con){
con.accept(money);
}

@Test
public void test02(){
List<String> list = Arrays.asList("北京", "南京", "天津", "东京", "西京");
List<String> stringList = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(stringList);
System.out.println("********");
List<String> stringList1 = filterString(list, (s) -> {
return s.contains("京");
});
System.out.println(stringList1);

}

//根据给定的规则,过滤集合中的字符串,此方法由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){
ArrayList<String> filterString = new ArrayList<>();
for (String s :list){
if (pre.test(s)) {
filterString.add(s);
}
}
return filterString;
}
}

输出结果

1
2
3
4
5
6
7
花了400
********
花了400

[北京, 南京, 东京, 西京]
********
[北京, 南京, 东京, 西京]

image-20200218145057168

总结

何时使用lambda表达式?

当需要对一个函数式接口实例化的时候,可以使用lambda表达式。

何时使用给定的函数式接口?

如果我们开发中需要定义一个函数式接口,首先看看在已有的jdk提供的函数式接口是否提供了

能满足需求的函数式接口。如果有,则直接调用即可,不需要自己再自定义了。

方法引用

方法引用

理解

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

使用情境

当要传递给Lambda体的操作,已经实现的方法了,可以使用方法引用

要求

实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的 方法的参数列表和返回值类型保持一致

格式

类(或对象) ::方法名

分为如下的三种情况

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

情况2 类 :: 静态方法

情况3 类 :: 非静态方法

要求

要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!(针对于情况1和情况2)

当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName(针对于情况3)

使用建议

如果给函数式接口提供实例,恰好满足方法引用的使用情境,大家就可以考虑使用方法引用给函数式接口提供实例。如果大家不熟悉方法引用,那么还可以使用lambda表达式

使用举例

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
// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");

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

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

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

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

}
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
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
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 test4() {
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));
}
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
// 情况:类 :: 实例方法  (难度)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
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 test6() {
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 test7() {
Employee employee = new Employee(1001, "Jerry", 23, 6000);


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));


}

构造器引用与数组引用

构造器引用格式

类名::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 test1(){

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 test2(){
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 test3(){
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,"Tom"));

}

数组引用格式

数组类型[] :: new

数组引用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Function中的R apply(T t)
@Test
public void test4(){
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

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

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

Stream API的理解

Stream关注的是对数据的运算,与CPU打交道,集合关注的是数据的存储,与内存打交道

java8提供了一套api,使用这套api可以对内存中的数据进行过滤、排序、映射、归约等操作。类似于sql对数据库中表的相关操作

Stream API 提供了一种高效且易于使用的处理数据的方式

为什么要使用Stream API

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

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

注意点

①Stream 自己不会存储元素

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

Stream的使用流程

① Stream的实例化

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

② 一系列的中间操作(过滤、映射、…)

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

③ 终止操作

一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

image-20200218162441757

使用流程的注意点

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

一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

Stream实例化

创建 Stream方式一:通过集合

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

default Stream<E> stream()返回一个顺序流

default Stream<E> parallelStream()返回一个并行流

1
2
3
4
5
6
7
8
9
10
11
12
//创建 Stream方式一:通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();

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

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

}

创建 Stream方式二:通过数组

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

static <T> Stream<T> stream(T[] array)返回一个流

重载形式,能够处理对应基本类型的数组

public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建 Stream方式二:通过数组
@Test
public void test2(){
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);

}

创建 Stream方式三:通过Stream的of()

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

public static<T> Stream<T> of(T... values) 返回一个流

1
2
3
4
5
6
7
//创建 Stream方式三:通过Stream的of()
@Test
public void test3(){

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

}

创建 Stream方式四:创建无限流

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

迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
生成
public static<T> Stream<T> generate(Supplier<T> s)

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建 Stream方式四:创建无限流
@Test
public void test4(){
//迭代
//public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);

//生成
//public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);

}

中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”

img

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 test1(){
List<Employee> employees = EmployeeData.getEmployees();
//filter(Predicate p) 接收 Lambda ,从流中排除某些元素
Stream<Employee> stream = employees.stream();
//查询员工表中薪资大于7000的员工信息
stream.filter((e)->{return e.getSalary()>7000;}).forEach(System.out::println);
System.out.println();
//limit(long maxSize) 截断流,使其元素不超过给定数量
//查询员工表中前三个数据
employees.stream().limit(3).forEach(System.out::println);
System.out.println();
//skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
employees.stream().skip(3).forEach(System.out::println);
System.out.println();
//distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
employees.add(new Employee(1010,"刘强东",40,8000));
employees.add(new Employee(1010,"刘强东",40,8000));
employees.add(new Employee(1010,"刘强东",40,8000));
employees.add(new Employee(1010,"刘强东",40,8000));
employees.stream().distinct().forEach(System.out::println);
System.out.println();
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}

Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}

Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}

Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1010, name='刘强东', age=40, salary=8000.0}

img

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
//映射
@Test
public void test2(){
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
//map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
//将list中的所有数据大写
list.stream().map(str->str.toUpperCase()).forEach(System.out::println);
//获取员工姓名长度大于3的员工的姓名
List<Employee> employees = EmployeeData.getEmployees();
//Employee::getName
Stream<String> nameStream = employees.stream().map((employee) -> {
return employee.getName();
});
nameStream.filter(name->name.length()>3).forEach(System.out::println);

System.out.println();
//列出所有的字符
Stream<Stream<Character>> streamStream = list.stream().map(StreamTest1::fromStringToStream);
streamStream.forEach(s->{s.forEach(System.out::println);});

//flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
//列出所有的字符
System.out.println();
Stream<Character> characterStream = list.stream().flatMap(StreamTest1::fromStringToStream);
characterStream.forEach(System.out::println);
}

//将字符串中的多个字符构成的集合转换为对应的Stream实例
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for (Character c:str.toCharArray()){
list.add(c);
}
return list.stream();
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
AA
BB
CC
DD
比尔盖茨
扎克伯格

a
a
b
b
c
c
d
d

a
a
b
b
c
c
d
d

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//排序
@Test
public void test03(){
//sorted() 产生一个新流,其中按自然顺序排序
List<Integer> list = Arrays.asList(12, 43, 65, 24, 87, 0, -34, 5);
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);
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-34
0
5
12
24
43
65
87
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}

终止操作

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

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

img

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 test1(){
List<Employee> employees = EmployeeData.getEmployees();
//allMatch(Predicate p) 检查是否匹配所有元素
//练习:是否所有的员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(employee -> {
return employee.getAge() > 18;
});
System.out.println(allMatch);
//anyMatch(Predicate p) 检查是否至少匹配一个元素
boolean anyMatch = employees.stream().anyMatch(employee -> employee.getSalary() > 10000);
System.out.println(anyMatch);
//noneMatch(Predicate p) 检查是否没有匹配所有元素
boolean noneMatch = employees.stream().noneMatch(employee -> employee.getName().startsWith("雷"));
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);

}

输出结果

1
2
3
4
5
false
false
false
Optional[Employee{id=1001, name='马化腾', age=34, salary=6000.38}]
Optional[Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}]

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void test2(){
//count() 返回流中元素总数
List<Employee> employees = EmployeeData.getEmployees();
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
System.out.println();
//max(Comparator c) 返回流中最大值
//返回最高的工资
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> max = salaryStream.max(Double::compareTo);
System.out.println(max);
System.out.println();
//min(Comparator c) 返回流中最小值
//返回最低工资的员工
//forEach(Consumer c)
Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(employee);
System.out.println();
//内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
employees.stream().forEach(System.out::println);
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
5

Optional[9876.12]

Optional[Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}]

Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//归约
@Test
public void test3(){
//reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
//练习1:计算1-10的自然数的和
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 b) 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//练习2:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
Optional<Double> reduce = salaryStream.reduce((d1, d2) -> d1 + d2);
System.out.println(reduce);
}

输出结果

1
2
55
Optional[48424.08]

img

1
2
3
4
5
6
7
8
9
10
11
12
13
//收集
@Test
public void test4(){
//collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总的方法
//练习1:查询工资大于6000的员工,结果返回一个List或set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out::println);
System.out.println();
Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
employeeSet.forEach(System.out::println);

}

输出结果

1
2
3
4
5
6
7
8
9
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}

Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1004, name='雷军', age=26, salary=7657.37}

Collector需要使用Collectors提供实例

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

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

image-20200218162821378

Optional类的使用

java.util.Optional类

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,

Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序

员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分

理解

为了解决java中的空指针问题而生

Optional<T> 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null

,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避

免空指针异常

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

用get()方法会返回该对象

常用方法

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
@Test
public void test1(){
//empty():创建的Optional对象内部的value = null
Optional<Object> op1 = Optional.empty();
if(!op1.isPresent()){//Optional封装的数据是否包含数据
System.out.println("数据为空");

}
System.out.println(op1);
System.out.println(op1.isPresent());
//如果Optional封装的数据value为空,则get()报错。否则,value不为空时,返回value.
//System.out.println(op1.get());

}

@Test
public void test2(){
String str = "hello";
//str = null;
//of(T t):封装数据t生成Optional对象。要求t非空,否则报错。
Optional<String> op1 = Optional.of(str);
//get()通常与of()方法搭配使用。用于获取内部的封装的数据value
String str1 = op1.get();
System.out.println(str1);

}

@Test
public void test3(){
String str = "beijing";
str = null;
//ofNullable(T t) :封装数据t赋给Optional内部的value。不要求t非空
Optional<String> op1 = Optional.ofNullable(str);
//orElse(T t1):如果Optional内部的value非空,则返回此value值。如果
//value为空,则返回t1.
String str2 = op1.orElse("shanghai");

System.out.println(str2);


}
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
public class OptionalTest {
/*
Optional.of(T t) : 创建一个 Optional 实例,t必须非空,否则报错
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):t可以为null
*/
@Test
public void test1(){
Girl girl = new Girl();
//of(T t):保证t是非空的
Optional<Girl> optionalGirl = Optional.of(girl);
}

@Test
public void test2(){
Girl girl = new Girl();
girl=null;
//ofNullable(T t);t可以是null
Optional<Girl> optionalGirl = Optional.ofNullable(girl);
System.out.println(optionalGirl);

//orElse(T t1):如果当前的optional内部封装的t是非空的,则返回内部的t,如果内部t是空的,则返回orElse()方法中的参数t1
Girl girl1 = optionalGirl.orElse(new Girl("张小于"));
System.out.println(girl1);
}

public String getGirlName(Boy boy){
return boy.getGirl().getName();
}
@Test
public void test3(){
//出现空指针问题
Boy boy = new Boy();
boy=null;
String girlName = getGirlName(boy);
System.out.println(girlName);
}

//优化以后的方法
public String getGirlName1(Boy boy){
if (boy!=null) {
Girl girl = boy.getGirl();
if (girl!=null) {
return girl.getName();
}
}
return null;
}

}

输出结果

1
2
3
4
5
6
7
8
test1没有输出结果

Optional.empty
Girl{name='张小于'}

java.lang.NullPointerException
at StreamTest.java4.OptionalTest.getGirlName(OptionalTest.java:34)
at StreamTest.java4.OptionalTest.test3(OptionalTest.java:41)

典型练习

能保证如下的方法执行中不会出现空指针的异常

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
//使用Optional类的getGirlName():
public String getGirlName2(Boy boy){

Optional<Boy> boyOptional = Optional.ofNullable(boy);
//此时的boy1一定非空
Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));

Girl girl = boy1.getGirl();

Optional<Girl> girlOptional = Optional.ofNullable(girl);
//girl1一定非空
Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));

return girl1.getName();
}

@Test
public void test5(){
Boy boy = null;
boy = new Boy();
boy = new Boy(new Girl("苍老师"));
String girlName = getGirlName2(boy);
System.out.println(girlName);

}
文章目录
  1. 1. Java8新特性
    1. 1.1. Java8新特性简介
    2. 1.2. Lambda表达式
      1. 1.2.1. Lambda表达式使用前后的对比
    3. 1.3. Lambda表达式的基本语法
    4. 1.4. 如何使用
    5. 1.5. 函数式接口
      1. 1.5.1. 什么是函数式接口
      2. 1.5.2. 函数式接口的使用说明
      3. 1.5.3. 自定义函数式接口
      4. 1.5.4. Java8中关于Lambda表达式提供的4个基本的函数式接口
      5. 1.5.5. 总结
    6. 1.6. 方法引用
      1. 1.6.1. 理解
      2. 1.6.2. 使用情境
      3. 1.6.3. 格式
      4. 1.6.4. 分为如下的三种情况
      5. 1.6.5. 要求
      6. 1.6.6. 使用建议
      7. 1.6.7. 使用举例
    7. 1.7. 构造器引用与数组引用
      1. 1.7.1. 构造器引用格式
      2. 1.7.2. 构造器引用使用要求
      3. 1.7.3. 构造器引用举例
      4. 1.7.4. 数组引用格式
      5. 1.7.5. 数组引用举例
    8. 1.8. Stream API
      1. 1.8.1. Stream API的理解
      2. 1.8.2. 为什么要使用Stream API
      3. 1.8.3. 注意点
      4. 1.8.4. Stream的使用流程
      5. 1.8.5. 使用流程的注意点
      6. 1.8.6. Stream实例化
        1. 1.8.6.1. 创建 Stream方式一:通过集合
        2. 1.8.6.2. 创建 Stream方式二:通过数组
        3. 1.8.6.3. 创建 Stream方式三:通过Stream的of()
        4. 1.8.6.4. 创建 Stream方式四:创建无限流
      7. 1.8.7. 中间操作
      8. 1.8.8. 终止操作
    9. 1.9. Optional类的使用
      1. 1.9.1. 理解
      2. 1.9.2. 常用方法
      3. 1.9.3. 典型练习
|