1. Java 8 函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:
1 @FunctionalInterface
2 interface GreetingService
3 {
4 void sayMessage(String message);
5 }
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
1 GreetingService greetService1 = message -> System.out.println("Hello " + message);
2.Lambda表达式
2.1 Lambda简介
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
2.2 对接口的要求
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
2.3 @FunctionalInterface
修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
2.4 Lambda 基础语法
我们这里给出六个接口,后文的全部操作都利用这六个接口来进行阐述。
1 /**多参数无返回*/
2 @FunctionalInterface
3 public interface NoReturnMultiParam {
4 void method(int a, int b);
5 }
6
7 /**无参无返回值*/
8 @FunctionalInterface
9 public interface NoReturnNoParam {
10 void method();
11 }
12
13 /**一个参数无返回*/
14 @FunctionalInterface
15 public interface NoReturnOneParam {
16 void method(int a);
17 }
18
19 /**多个参数有返回值*/
20 @FunctionalInterface
21 public interface ReturnMultiParam {
22 int method(int a, int b);
23 }
24
25 /*** 无参有返回*/
26 @FunctionalInterface
27 public interface ReturnNoParam {
28 int method();
29 }
30
31 /**一个参数有返回值*/
32 @FunctionalInterface
33 public interface ReturnOneParam {
34 int method(int a);
35 }
语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
基础语法
1 public class Test1 {
2 public static void main(String[] args) {
3
4 // 1.无参数无返回
5 NoReturnNoParam noReturnNoParam = () -> {
6 System.out.println("无参数无返回");
7 };
8 noReturnNoParam.method();
9
10 // 2.一个参数无返回
11 NoReturnOneParam noReturnOneParam = (a) -> {
12 System.out.println("一个参数无返回 a =" + a*2);
13 };
14 noReturnOneParam.method(6);
15
16 // 3.多个参数无返回
17 NoReturnMultiParam noReturnMultiParam = (a, b) -> {
18 System.out.println("多个参数无返回值 " + (a + b));
19 };
20 noReturnMultiParam.method(3,4);
21
22 // 4.无参数有返回值
23 ReturnNoParam returnNoParam = () -> {
24 System.out.println("无参数有返回值");
25 return 5;
26 };
27 int b = returnNoParam.method();
28 System.out.println("b = "+b);
29
30 // 5.一个参数有返回值
31 ReturnOneParam returnOneParam = (a) -> {
32 System.out.println("一个参数有返回值");
33 return a + 5;
34 };
35 int a = returnOneParam.method(6);
36 System.out.println("a = " + a);
37
38 // 6.多个参数有返回值
39 ReturnMultiParam returnMultiParam = (c, d) -> {
40 System.out.println("c - d = " + (c - d));
41 return c - d;
42 };
43 int result = returnMultiParam.method(8, 2);
44 System.out.println("result = " + result);
45 }
46 }
Lambda 语法简化
我们可以通过观察以下代码来完成代码的进一步简化,写出更加优雅的代码。
1 public class Test2 {
2 public static void main(String[] args) {
3 //1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
4 NoReturnMultiParam lamdba1 = (a, b) -> {
5 System.out.println("简化参数类型");
6 System.out.println("简化参数类型");
7 };
8 lamdba1.method(1, 2);
9
10 // 2.省略参数小括号,如果只有一个参数,则可以简化参数括号
11 NoReturnOneParam lambda2 = a -> {
12 System.out.println("如果只有一个入参,可以简化参数 a = " + a);
13 };
14 lambda2.method(9);
15
16 // 3. 如果方法体只有一条语句,则可以省略花括号
17 NoReturnOneParam lambda3 = b -> System.out.println("b = " + (b + 6));
18 lambda3.method(6);
19
20 //4.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号
21 ReturnOneParam lambda4 = a -> a + 3;
22 System.out.println(lambda4.method(5));
23
24 //5.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号
25 ReturnMultiParam lambda5 = (a , b) -> a + b - 1;
26 System.out.println(lambda5.method(6, 10));
27 }
28 }
2.5 Lambda 表达式常用示例
lambda 表达式引用方法
有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。
语法
方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象
1 public class Exe1 {
2 public static void main(String[] args) {
3 ReturnOneParam lambda1 = a -> doubleNum(a);
4 System.out.println(lambda1.method(3));
5
6 //lambda2 引用了已经实现的 doubleNum 方法
7 ReturnOneParam lambda2 = Exe1::doubleNum;
8 System.out.println(lambda2.method(3));
9
10 Exe1 exe = new Exe1();
11
12 //lambda4 引用了已经实现的 addTwo 方法
13 ReturnOneParam lambda4 = exe::addTwo;
14 System.out.println(lambda4.method(2));
15 }
16
17 /**
18 * 要求
19 * 1.参数数量和类型要与接口中定义的一致
20 * 2.返回值类型要与接口中定义的一致
21 */
22 public static int doubleNum(int a) {
23 return a * 2;
24 }
25
26 public int addTwo(int a) {
27 return a + 2;
28 }
29 }
构造方法的引用
一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。
1 interface ItemCreatorBlankConstruct {
2 Item getItem();
3 }
4 interface ItemCreatorParamContruct {
5 Item getItem(int id, String name, double price);
6 }
7
8 public class Exe2 {
9 public static void main(String[] args) {
10 ItemCreatorBlankConstruct creator = () -> new Item();
11 Item item = creator.getItem();
12
13 ItemCreatorBlankConstruct creator2 = Item::new;
14 Item item2 = creator2.getItem();
15
16 ItemCreatorParamContruct creator3 = Item::new;
17 Item item3 = creator3.getItem(112, "鼠标", 135.99);
18 }
19 }
lambda 表达式创建线程
我们以往都是通过创建 Thread 对象,然后通过匿名内部类重写 run() 方法,一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。
1 Thread t = new Thread(() -> {
2 for (int i = 0; i < 10; i++) {
3 System.out.println(2 + ":" + i);
4 }
5 });
6 t.start();
遍历集合
我们可以调用集合的 public void forEach(Consumer<? super E> action)
方法,通过 lambda 表达式的方式遍历集合中的元素。以下是 Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。
1 @FunctionalInterface
2 public interface Consumer<T> {
3 void accept(T t);
4 //....
5 }
1 ArrayList<Integer> list = new ArrayList<>();
2
3 Collections.addAll(list, 1,2,3,4,5);
4
5 //lambda表达式 方法引用
6 list.forEach(System.out::println);
7
8 list.forEach(element -> {
9 if (element % 2 == 0) {
10 System.out.println(element);
11 }
12 });
删除集合中的某个元素
我们通过public boolean removeIf(Predicate<? super E> filter)
方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
1 ArrayList<Item> items = new ArrayList<>();
2 items.add(new Item(11, "小牙刷", 12.05 ));
3 items.add(new Item(5, "日本马桶盖", 999.05 ));
4 items.add(new Item(7, "格力空调", 888.88 ));
5 items.add(new Item(17, "肥皂", 2.00 ));
6 items.add(new Item(9, "冰箱", 4200.00 ));
7
8 items.removeIf(ele -> ele.getId() == 7);
9
10 //通过 foreach 遍历,查看是否已经删除
11 items.forEach(System.out::println);
集合内元素的排序
在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
1 ArrayList<Item> list = new ArrayList<>();
2 list.add(new Item(13, "背心", 7.80));
3 list.add(new Item(11, "半袖", 37.80));
4 list.add(new Item(14, "风衣", 139.80));
5 list.add(new Item(12, "秋裤", 55.33));
6
7 /*
8 list.sort(new Comparator<Item>() {
9 @Override
10 public int compare(Item o1, Item o2) {
11 return o1.getId() - o2.getId();
12 }
13 });
14 */
15
16 list.sort((o1, o2) -> o1.getId() - o2.getId());
17
18 System.out.println(list);
3. java8流式处理
3.1 简介
在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现。比如我们希望对一个包含整数的集合中筛选出所有的偶数,并将其封装成为一个新的List返回,那么在java8之前,我们需要通过如下代码实现:
1 List<Integer> evens = new ArrayList<>();
2 for (final Integer num : nums) {
3 if (num % 2 == 0) {
4 evens.add(num);
5 }
6 }
通过java8的流式处理,我们可以将代码简化为:
1 List<Integer> evens = nums.stream().filter(num -> num % 2 == 0).collect(Collectors.toList());
先简单解释一下上面这行语句的含义,stream()
操作将集合转换成一个流,filter()
执行我们自定义的筛选处理,这里是通过lambda表达式筛选出所有偶数,最后我们通过collect()
对结果进行封装处理,并通过Collectors.toList()
指定其封装成为一个List集合返回。
由上面的例子可以看出,java8的流式处理极大的简化了对于集合的操作,实际上不光是集合,包括数组、文件等,只要是可以转换成流,我们都可以借助流式处理,类似于我们写SQL语句一样对其进行操作。java8通过内部迭代来实现对流的处理,一个流式处理可以分为三个部分:转换成流、中间操作、终端操作。如下图:
以集合为例,一个流式处理的操作我们首先需要调用stream()
函数将其转换成流,然后再调用相应的中间操作
达到我们需要对集合进行的操作,比如筛选、转换等,最后通过终端操作
对前面的结果进行封装,返回我们需要的形式。
3.2 我们定义一个简单的学生实体类,用于后面的例子演示:
1 package Lambda.stream;
2
3 /**
4 * @author linliquan
5 * @description:
6 * @create 2020/12/18 14:16
7 */
8 public class Student {
9
10 /** 学号 */
11 private long id;
12
13 private String name;
14
15 private int age;
16
17 /** 年级 */
18 private int grade;
19
20 /** 专业 */
21 private String major;
22
23 /** 学校 */
24 private String school;
25
26 public Student(long id,String name, int age, int grade, String major, String school){
27 this.id = id;
28 this.name = name;
29 this.age = age;
30 this.grade = grade;
31 this.major = major;
32 this.school = school;
33 }
34
35 public long getId() {
36 return id;
37 }
38
39 public void setId(long id) {
40 this.id = id;
41 }
42
43 public String getName() {
44 return name;
45 }
46
47 public void setName(String name) {
48 this.name = name;
49 }
50
51 public int getAge() {
52 return age;
53 }
54
55 public void setAge(int age) {
56 this.age = age;
57 }
58
59 public int getGrade() {
60 return grade;
61 }
62
63 public void setGrade(int grade) {
64 this.grade = grade;
65 }
66
67 public String getMajor() {
68 return major;
69 }
70
71 public void setMajor(String major) {
72 this.major = major;
73 }
74
75 public String getSchool() {
76 return school;
77 }
78
79 public void setSchool(String school) {
80 this.school = school;
81 }
82
83 @Override
84 public String toString() {
85 return "Student{" +
86 "id=" + id +
87 ", name='" + name + '\'' +
88 ", age=" + age +
89 ", grade=" + grade +
90 ", major='" + major + '\'' +
91 ", school='" + school + '\'' +
92 '}';
93 }
94 }
3.3 各种流式骚操作
1 package Lambda.stream;
2
3 import java.util.*;
4 import java.util.stream.Collectors;
5
6 /**
7 * @author linliquan
8 * @description: java8 流式处理
9 * @create 2020/12/20 22:02
10 */
11 public class Test {
12 public static void main(String[] args) {
13 // 初始化
14 List<Student> studentList = new ArrayList<Student>() {
15 {
16 add(new Student(20160001, "孔明", 20, 1, "土木工程", "武汉大学"));
17 add(new Student(20160001, "伯约", 21, 2, "信息安全", "武汉大学"));
18 add(new Student(20160003, "玄德", 22, 3, "经济管理", "武汉大学"));
19 add(new Student(20160004, "云长", 21, 2, "信息安全", "武汉大学"));
20 add(new Student(20161001, "翼德", 21, 2, "机械与自动化", "华中科技大学"));
21 add(new Student(20161002, "元直", 20, 4, "土木工程", "华中科技大学"));
22 add(new Student(20162003, "奉孝", 23, 4, "计算机科学", "华中科技大学"));
23 add(new Student(20162001, "仲谋", 18, 3, "土木工程", "浙江大学"));
24 add(new Student(20162002, "鲁肃", 23, 1, "计算机科学", "浙江大学"));
25 add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大学"));
26 }
27 };
28
29 /**
30 * 各种骚操作
31 */
32 // 1 过滤
33 // 1.1 filter 过滤 ,筛选出学校为武汉大学的数据
34 List<Student> filterList = studentList.stream().filter(student -> "武汉大学".equals(student.getSchool())).collect(Collectors.toList());
35 filterList.forEach(System.out::println);
36 System.out.println();
37
38 // 1.2 distinct 去重,筛选出年级为偶数的数据
39 List<Student> ageList = studentList.stream().filter(student -> student.getAge() % 2 == 0).distinct().collect(Collectors.toList());
40 ageList.forEach(System.out::println);
41 System.out.println();
42
43 // 1.3 limit,查询结果中,返回前两条数据
44 List<Student> limitList = studentList.stream().filter(student -> student.getAge() % 2 == 0).distinct().limit(2).collect(Collectors.toList());
45 limitList.forEach(System.out::println);
46 System.out.println();
47
48 // 1.4 sorted 筛选出专业为土木工程的学生,并按年龄从小到大排序,筛选出年龄最小的两个学生,那么可以实现为:
49 List<Student> sortedList = studentList.stream().filter(student -> "土木工程".equals(student.getMajor()))
50 .sorted(Comparator.comparingInt(Student::getAge)).limit(2).collect(Collectors.toList());
51 sortedList.forEach(System.out::println);
52 System.out.println();
53
54 // 1.5 skip 跳过 skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素。跳过前面两条数据。
55 List<Student> skipList = studentList.stream().filter(student -> "土木工程".equals(student.getMajor()))
56 .sorted(Comparator.comparingInt(Student::getAge).reversed()).limit(2).collect(Collectors.toList());
57 skipList.forEach(System.out::println);
58 System.out.println();
59
60 // 2 映射 在SQL中,借助SELECT关键字后面添加需要的字段名称,可以仅输出我们需要的字段数据,而流式处理的映射操作也是实现这一目的,
61 // 在java8的流式处理中,主要包含两类映射操作:map和flatMap。
62
63 // 2.1 map ,假设我们希望筛选出所有专业为计算机科学的学生姓名,那么我们可以在filter筛选的基础之上,通过map将学生实体映射成为学生姓名字符串
64 List<String> nameList = studentList.stream().filter(student -> "计算机科学".equals(student.getMajor())).map(Student::getName).collect(Collectors.toList());
65 nameList.forEach(e -> {
66 System.out.println(e);
67 });
68 System.out.println();
69
70 // 计算所有专业为计算机科学学生的年龄之和
71 int ageSum = studentList.stream().filter(student -> "计算机科学".equals(student.getMajor())).mapToInt(Student::getAge).sum();
72 System.out.println(ageSum);
73 System.out.println();
74
75 // flatMap flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流
76 // 假设我们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};,我们希望输出构成这一数组的所有非重复字符,
77 // 那么我们可能首先会想到如下实现:
78 String[] strs = {"java8", "is", "easy", "to", "use"};
79 List<String[]> stringsList = Arrays.stream(strs).map(str -> str.split("")).collect(Collectors.toList());
80 stringsList.forEach(strings -> {
81 for (int i = 0; i < strings.length; i++) {
82 System.out.print(strings[i] + " ");
83 }
84 System.out.println();
85 });
86
87 // 正确的实现
88 List<String> distinctStr = Arrays.stream(strs).map(s -> s.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
89 System.out.println();
90 distinctStr.forEach(s -> System.out.print(s));
91 System.out.println();
92
93 // 筛选出专业为土木工程的数据
94 List<Student> collect = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).collect(Collectors.toList());
95 collect.forEach(System.out::println);
96 System.out.println();
97
98 // 3 终端操作
99 // 3.1 allMatch 全部匹配,专业为土木工程,且年龄为20
100 boolean allMatch = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).limit(2)
101 .allMatch(student -> student.getAge() == 20);
102 System.out.println(allMatch);
103
104 // 3.2 anyMatch 一个或多个满足。是否存在专业为土木工程,且其中一个或多个年龄为18
105 boolean ageIs18 = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).anyMatch(student -> student.getAge() == 18);
106 System.out.println(ageIs18);
107
108 // 3.3 noneMathch 不存在,不满足。专业为土木工程,且其中不存在年龄为18
109 boolean noneMatch = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).noneMatch(student -> student.getAge() == 18);
110 System.out.println(noneMatch);
111
112 // 3.4 findFirst 返回满足的第一个
113 Optional<Student> first = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();
114 System.out.println(first);
115 System.out.println();
116
117 // 3.5 findAny 返回任意一个
118 Optional<Student> any = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).findAny();
119 System.out.println(any);
120 System.out.println();
121
122 // 3.6 reduce 归约,计算年龄之和
123 Integer ageReduce = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).map(Student::getAge).reduce(0, (a, b) -> a + b);
124 System.out.println(ageReduce);
125 System.out.println();
126
127 // 计算年龄之和
128 Integer ageReduce2 = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).map(Student::getAge).reduce(0, Integer::sum);
129 System.out.println(ageReduce2);
130 System.out.println();
131
132 // 4 收集
133 // 4.1 归约 求学生的总人数
134 Long studentCount = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).count();
135 System.out.println(studentCount);
136
137 // 4.2 求年龄的最大值,法一
138 Optional<Student> ageMax = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
139 System.out.println(ageMax);
140
141 // 4.3 求最小年龄,法二
142 Optional<Student> ageMax2 = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).min(Comparator.comparing(Student::getAge));
143 System.out.println(ageMax2);
144
145 // 4.4 求年龄总和,法一
146 Integer ageTotal = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).collect(Collectors.summingInt(Student::getAge));
147 System.out.println(ageTotal);
148
149 // 求年龄总和,法二
150 Integer ageTotal2 = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).mapToInt(Student::getAge).sum();
151 System.out.println(ageTotal2);
152
153 // 4.5 求年龄的平均值
154 double avgAge = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).collect(Collectors.averagingInt(Student::getAge));
155 System.out.println(avgAge);
156
157 // 4.6 一次性得到年龄的个数、总和、均值、最大值、最小值
158 IntSummaryStatistics summaryStatistics = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).collect(Collectors.summarizingInt(Student::getAge));
159 System.out.println(summaryStatistics);
160
161 // 4.7 字符串拼接
162 String name = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).map(Student::getName).collect(Collectors.joining());
163 System.out.println(name);
164
165 // 4.8 字符串拼接, 逗号分隔
166 String name2 = studentList.stream().filter(student -> "土木工程".equals(student.getMajor())).map(Student::getName).collect(Collectors.joining(","));
167 System.out.println(name2);
168
169 // 5 分组
170 // 5.1 groupingBy,按专业进行分组
171 Map<String, List<Student>> listMap = studentList.stream().collect(Collectors.groupingBy(Student::getMajor));
172 for (Map.Entry<String, List<Student>> entry : listMap.entrySet()) {
173 System.out.println(entry.getKey() + " "+ entry.getValue());
174 }
175
176 // 5.2 多级分组,按专业进行分组,再按年龄进行降序
177 System.out.println("多级分组");
178 Map<String, List<Student>> groupingByMap = studentList.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.groupingBy(Student::getMajor));
179 for (Map.Entry<String, List<Student>> listEntry : groupingByMap.entrySet()) {
180 System.out.println(listEntry);
181 }
182
183 // 5.3 先按专业分组,再按年龄排序
184 System.out.println();
185 Map<Integer, List<Student>> gradeGroupingByMap = studentList.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.groupingBy(Student::getGrade));
186 for (Map.Entry<Integer, List<Student>> stringListEntry : gradeGroupingByMap.entrySet()) {
187 System.out.println(stringListEntry);
188 }
189
190 // 5.4 先按年龄排序升降序,然后再按年级升序
191 System.out.println("先按年龄排序升降序,然后再按年级升序");
192 List<Student> ageAndgradeList = studentList.stream().sorted(Comparator.comparing(Student::getAge).reversed().thenComparing(Student::getGrade)).collect(Collectors.toList());
193 ageAndgradeList.forEach(System.out::println);
194 System.out.println();
195
196 // 5.5 先按年龄排序升序,然后再按年级降序
197 System.out.println("先按年龄排序升序,然后再按年级降序");
198 List<Student> list = studentList.stream().sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getGrade,Comparator.reverseOrder())).collect(Collectors.toList());
199 list.forEach(System.out::println);
200 System.out.println();
201
202 }
203 }