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通过内部迭代来实现对流的处理,一个流式处理可以分为三个部分:转换成流、中间操作、终端操作。如下图:

java流式处理缺点_java

 

 

以集合为例,一个流式处理的操作我们首先需要调用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 }