Fork me on GitHub

Java8 新特性

注意:所有文章除特别说明外,转载请注明出处.

[TOC]

Java8 新特性

==一、Lambda 表达式==

1. Lambda 表达式使用前后对比

使用前:

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 test1() {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门!");
}
};
r1.run();
}
//例二:
@Test
public void test2() {
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
con.accept("熙熙攘攘");
}

使用后:

1
2
3
4
5
6
7
8
9
10
11
12
//例一:	
@Test
public void test1() {
Runnable r2 = () -> System.out.println("我爱这三秦大地!");
r2.run();
}
//例二:
@Test
public void test2() {
Consumer<String> con2 = (String t) -> System.out.println(t);
con2.accept("多么美丽");
}

2.Lambda 表达式的基本语法

1
2
3
4
5
6
*   1. 举例:
* (o1, o2) -> Integer.compare(o1, o2);
* 2. 格式:
* -> :Lambda操作符 或 箭头操作符
* -> 左边: Lambda形参列表(其实就是接口中的抽象方法的形参列表)
* -> 右边: Lambda 体(其实就是重写的抽象方法的方法体)

3.Lambda 表达式的使用(6种情况)

无参,无返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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
@Test
public void test2() {
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
con.accept("熙熙攘攘");

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

Consumer<String> con2 = (String t) -> System.out.println(t);
con2.accept("多么美丽");
}

类型推断。

1
2
3
4
5
6
7
8
9
10
@Test
public void test3() {
Consumer<String> con = (String t) -> System.out.println(t);
con.accept("西安城墙");

System.out.println("*********************");
//类型推断
Consumer<String> con2 = (t) -> System.out.println(t);
con2.accept("威武雄壮");
}

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

1
2
3
4
5
6
7
8
9
10
@Test
public void test4() {
Consumer<String> con = (String t) -> System.out.println(t);
con.accept("西安城墙");

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

Consumer<String> con1 = t -> System.out.println(t);
con1.accept("威武雄壮");
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test5() {
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(com1.compare(12, 21));
System.out.println("******************************");
Comparator<Integer> com2 = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(12, 21));
}

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

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test6() {
Comparator<Integer> com1 = (o1, o2) -> {
return o1.compareTo(o2);
};
System.out.println(com1.compare(12, 21));

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

Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
System.out.println(com2.compare(12, 21));
}

4.总结:

-> 左边:Lambda 形参列表的参数类型可以省略(类型推断),如果形参列表只有一个参数,其一对() 也可以省略

-> 右边: Lambda体 应该使用一对{} 包裹,如果只有一条执行语句(可能是return 语句),可以省略大括号以及 return 关键字

5.Lambda 表达式的实质

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

6.何时使用Lambda表达式:

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

以前可以通过匿名实现类表示的现在都可以用Lambda表达式来书写.

7.常使用的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
29
30
31
32
33
34
35
//常用的Lambda 表达式
@Test
public void test4() {
// 集合遍历
List<Integer> list = Arrays.asList(1, 2, 8, 4, 9, 5, 7);
list.forEach(new Consumer<Integer>() {

public void accept(Integer t) {
System.out.println(t);
}
});

list.forEach(t -> System.out.print(t + " "));
list.forEach(System.out :: println);

// 集合排序
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});

list.sort((o1, o2) -> o1.compareTo(o2));
list.sort(Integer :: compareTo);
//开启线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
}).start();

new Thread( () -> System.out.println("run1")).start();
}

二、函数式接口

1.什么是函数接口:

如果一个接口只声明了一个抽象方法,那么这个接口就成为函数式接口,我们可以在接口上面使用@FunctionalInterface 这个注解,用来检查它是否是一个函数式接口

2.Java内置四大核心函数式接口

消费型接口:Consumer void accept(T t)
供给型接口:Supplier T get()
函数型接口:Function<T, R> R apply(T t)
断定型接口:Predicate boolean test(T t)

3.何时使用函数式接口:

如果我们的开发中需要定义一个函数式接口,首先看看在已有的JDK提供的函数式接口是否提供了能满足需求的函数式接口,如果有则直接使用即可。

三、方法引用

1.理解

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

2.使用场景

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

3.格式

​ 类(对象) :: 方法名

4.分为以下三种情况

对象 :: 非静态方法
类 :: 静态方法

类 :: 非静态方法

5.要求

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

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

6.使用建议

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

7.使用举例:

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
public class LambdaTest3 {

//情况一: 对象 :: 实例方法
//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("XiAn");

}

@Test
public void test2() {
Employee emp = new Employee(1001, "Tom", 23, 8000.00);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());

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

//情况二: 类 :: 静态方法
//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, 32));

System.out.println("***************************");
Comparator<Integer> com2 = Integer :: compare;
System.out.println(com1.compare(12, 32));
}

//Function 中的 R apply(T t)
//Math 中的 round (Double d)
@Test
public void test4() {
Function<Double, Long> func = new Function<Double, Long>() {

@Override
public Long apply(Double t) {
return Math.round(t);
}
};
System.out.println(func.apply(1.2324));
System.out.println("**************************");

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

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

}

//情况三: 类 :: 实例方法(有难度)
//Comparator 中的int compare(T t1, T t2)
//String 中的 int t1.compareTo(t2)
// t1 作为调用者 调用 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("abc", "abd"));
}

@Test
public void test6() {
BiPredicate<String, String> pre1 = (str1, str2) -> str1.equals(str2);
System.out.println(pre1.test("abc", "abc"));
BiPredicate<String, String> pre2 = String :: equals;
System.out.println(pre2.test("abc", "abc"));
}
}

四、构造器引用以及数组引用

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
//构造器引用:
//Supplier 中的 T get()
//Employee 中的无参构造器: Employee()
@Test
public void test1() {
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);
System.out.println(func1.apply(1001));

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

//数组引用
//Function 中的 R apply(T t)
@Test
public void test3() {
Function<Integer , String[]> function = length -> new String[length];
System.out.println(Arrays.toString(function.apply(3)));
Function<Integer , String[]> function1 = String[] :: new;
System.out.println(Arrays.toString(function1.apply(5)));
}

==五、Stream API==

1.Stream API 的理解

  • Stream 关注的是对数据的运算,与CPU打交道。集合关注的是对数据的存储,与内存打交道。
  • Java8 提供了一套api,使用这套api可以对内存中的数据进行过滤,排序,映射,规约等操作,类似于sql语句。

2.注意点

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

3.Stream的使用流程

①:Stream 的实例化
②:一系列的中间操作(过滤,映射,规约等)
③:终止操作。

4.使用流程的注意点

4.1 一个中间操作链,对数据源的数据进行处理。
4.2 一旦执行终止操作,就执行中间操作链,之后,不会再被使用

5.步骤一: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
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 StreamAPITest1 {

//创建 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 方式二:通过数组
@Test
public void test2() {
int[] arr = {1, 2, 3, 4, 5, 6, 7};
//调用Arrays类中的 static <T> Stream<T> stream(T[] array) 返回一个流
IntStream intStream = Arrays.stream(arr);

Employee e1 = new Employee(1001, "Tyrone");
Employee e2 = new Employee(1001, "Monkey");
Employee[] emps = {e1, e2};
Stream<Employee> stream1 = Arrays.stream(emps);

}

//创建 Stream 方式三:通过Stream的of()
@Test
public void test3() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8);
}

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

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

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
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
public class StreamAPITest2 {

/**
* 1. 筛选与切片
*/
@Test
public void test1() {

List<Employee> list = EmployeeData.getEmployees();

//filter ( Predicate p) —接收 Lambda , 从流中排除某些元素
Stream<Employee> stream = list.stream();
//练习查询员工工资大于 7000的 员工信息
stream.filter(e -> e.getSalary() > 7000).forEach(System.out :: println);
System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");
//limit — 截断流,使其元素不超过给定数量
//这里一定要重新生成一个stream
list.stream().limit(3).forEach(System.out :: println);
System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");

//skip—跳过元素,返回一个扔掉了前 n 个元素的流,若流中元素不足 n 个,则返回一个空流。
list.stream().skip(3).forEach(System.out :: println);

//distinct —筛选,通过流所生成的元素 hashcode 和 equals 去除重复元素。
list.stream().distinct();//其实就是元素去重而已
}
/**
* 2. 映射
*/
@Test
public void test2() {
//map(function f) 接受一个函数作为参数 将元素转换成其他形式的提取信息
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str ->str.toUpperCase()).forEach(System.out :: println);

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

//练习2:
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::StringToStream);
streamStream.forEach(s -> {
s.forEach(System.out :: println);
});
System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");

//flatMap(Function f) ——接收一个函数作为参数,将流中的每个值都转换称为另外一个流,然后把所有的流连接成一个流。
//适用于 Stream 套 Stream
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest2::StringToStream);
characterStream.forEach(System.out :: println);
}
//将字符串中的多个字符添加到 list 之中
public static Stream<Character> StringToStream(String str) {
ArrayList list = new ArrayList<>();
for(Character c : str.toCharArray() ) {
list.add(c);
}
return list.stream();
}

/**
* 3. 排序
*/
@Test
public void test3() {
//sorted() 自然排序
//sorted(Comparator com) 定制排序
List<Integer> list1 = Arrays.asList(343, 45, 56, 76, 23, 12, 5646, 44, -12, -56);
list1.stream().sorted().forEach(System.out :: println);

//将会抛出异常
//List<Employee> employees = EmployeeData.getEmployees();
//employees.stream().sorted().forEach(System.out :: println);
//定制排序
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((e1, e2) -> {
int index = Integer.compare(e1.getAge(), e2.getAge());
if(index != 0) {
return index;
} else {
return Double.compare(e1.getSalary(), e2.getSalary());
}
}).forEach(System.out :: println);
}
}

7.步骤三:终止操作

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
public class StreamAPITest3 {
/**
* 1. 匹配与查找
*/
@Test
public void test1() {
List<Employee> employees = EmployeeData.getEmployees();
// 检查是否匹配所有元素
//练习 : 是否所有的员工的年龄全大于 18 岁
System.out.println(employees.stream().allMatch(e -> e.getAge() > 18));
// 检查是否至少匹配一个元素
//是否存在员工工资大于 10000 的
System.out.println(employees.stream().anyMatch(e -> e.getSalary() > 10000));
// 检查是否没有匹配元素
//是否存在员工姓 “小”
System.out.println(employees.stream().allMatch(e -> e.getName().startsWith("小")));
// 返回第一个元素
Optional<Employee> first = employees.stream().findFirst();
System.out.println(first);
// 返回任意一个元素
Optional<Employee> any = employees.parallelStream().findAny();
System.out.println(any);
}
@Test
public void test2() {

List<Employee> employees = EmployeeData.getEmployees();
// 返回元素的个数
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
// 返回流中的最大值(最大年龄)
Stream<Double> doubleStream = employees.stream().map(employee -> employee.getSalary());
Optional<Double> max = doubleStream.max(Double::compareTo);
System.out.println(max);
//返回流中的最小值 (最小年龄的员工)
Optional<Employee> min = employees.stream().min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(min);
//内部迭代
employees.stream().forEach(System.out :: println);
System.out.println("=-=============================================");
employees.forEach(System.out :: println);
}
/**
* 2. 规约
*/
@Test
public void test3() {
// reduce() 可以将流中的元素反复结合起来,得到一个值,返回
//练习 : 计算 1 - 10 的和
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = integers.stream().reduce(0, Integer::sum);
System.out.println(sum);

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

/**
* 3. 收集
*/
@Test
public void test4() {
//collect() —— 将流转换为其他形式。接受一个Collector 接口的实现,用于给Stream
//练习: 查找工资大于 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);
}
}

六、Optional类的使用

1.理解:

==为了解决空指针而生==

Optional 类 是一个容器类,他可以保存类型T的值,代表这个值存在。或仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在Optional 可以更好地表达这个概念,而且可以避免空指针异常。

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
public class OptionalTest2 {
@Test
public void test1() {
//生成一个空的opti 对象, 里面的 value 为 null
Optional<Object> op1 = Optional.empty();
if(!op1.isPresent()) { //判断是否包含数据 (false 表示不包含)
System.out.println("数据为空");
}
System.out.println(op1);
//如果封装到数据 value 为空 则 get 报错
//System.out.println(op1.get());
}
@Test
public void test2() {
String str = "hello";
//把对象封装进 Optional , of(T t)方法要求 t 不能为空
//* @param value the value to be present, which must be non-null
Optional<String> op1 = Optional.of(str);
// get 通常与 of 搭配使用 用于 获取 内部的数据
System.out.println(op1.get());
}
@Test
public void test3() {
// String str = "beijing";
String str = null;
// ofNullable( T t) : 封装数据 t 赋给 Optional, 不要求 t 非空
Optional<String> op1 = Optional.ofNullable(str);
// orElse(T t) : 如果 Optional 内部的 value 为空,则返回 t,非空 返回 value
String str2 = op1.orElse("shanghai");
System.out.println(str2);
}
}

3.典型练习:

总之 Optional 就是为了尽可能避免 空指针异常而出现的

1
2
3
4
5
6
7
8
9
//使用 Optional 类
public String getGirlName2(Boy boy){
Optional<Boy> boyOptional = Optional.ofNullable(boy);
Boy boy1 = boyOptional.orElse(new Boy(new Girl("琪琪")));
Girl girl = boy1.getGirl();
Optional<Girl> girlOptional = Optional.ofNullable(girl);
Girl girl1 = girlOptional.orElse(new Girl("小芳"));
return girl1.getName();
}

本文标题:Java8 新特性

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 08:08:35

原始链接:http://bangjinhu.github.io/undefined/Java8 新特性/

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

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.