《Java8 流式编程》学习总结
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 }