用了多少年的java8了,Lambda表达式和stream流也经常用,但是也仅限于某些用法比较熟练,看见了 Function、Consumer 等函数式接口还是一脸懵逼,现在来全面总结一下java8这些新特性,也为自己后续查找做个备忘。如果你只是想看某些 method 的用法的话,可以直接通过目录跳转到指定位置。
目录
函数式接口与Lambda表达式
JUF(java.util.function)包下最基础的四大函数式接口
Consumer接口
Supplier接口
Function接口
Predicate接口
Stream流的用法
Stream流的创建
Stream流的中间操作
filter 过滤
distinct 去重
limit 截取返回结果的数量,从头开始
skip 跳过排在前面的n个值
peek 检查元素
map 映射
mapToInt
flatMap
sorted 排序
Stream流的终止操作
allMatch 所有元素都匹配
anyMatch 只要有任意一个元素匹配
noneMatch 是否没有任何元素匹配
findFirst 返回结果列表中的第一个元素
findAny 返回结果列表中的任意一个元素
count 计数
max 最大值
min 最小值
forEach 遍历
reduce 规约
collect 收集
toList 收集为list
toMap 收集为map
groupingBy 分组
joining 合并字符串
partitioningBy 分区
collectingAndThen 收集后进一步处理
reducing
函数式接口与Lambda表达式
这两个东西是配套使用的,如果某个 method 的参数是函数式接口,那么这个参数就可以传递一个Lambda表达式。
函数式接口说白了就是,只存在一个抽象方法的 interface 类,JDK中也提供了相应的注解来声明某个 interface 是一个函数式接口:@FunctionalInterface。比如 Runnable 接口,就是常见的函数式接口。我们自己也可以自定义函数式接口。
JDK内部定义了一些常用的函数式接口来供我们编程使用
JUF(java.util.function)包下最基础的四大函数式接口
- Consumer<T>
- Supplier<T>
- Function<T, R>
- Predicate<T>
接下来逐个分析各个接口,及其对应的 lambda 表达式
Consumer接口
Consumer接口的抽象方法为 accept , 接收一个参数,没有返回值,妥妥的消费型接口,只进不出。 lambda 表达式要来表达 accept 这个接口方法的话,大概就长这样
t -> {逻辑…}
例如下面这个例子
@Test
void testConsumer() {consumerMethod(t -> System.out.println("小明吃了" + t + "个苹果"));consumerMethod(t -> System.out.println("小张摘了" + t + "个桃子"));
}private void consumerMethod(Consumer<Integer> consumer) {consumer.accept(10);
}
输出结果:
小明吃了10个苹果
小张摘了10个桃子
另外Consumer接口中还有一个 andThen 方法,通过这个方法可以将一堆 Consumer 串起来,组成一个链式消费链路。
例子
@Test
void testConsumerAndThen() {Consumer<Integer> eat = t -> System.out.println("吃了"+t+"碗饭");Consumer<Integer> drink = t -> System.out.println("喝了"+t+"杯水");Consumer<Integer> sport = t -> System.out.println("运动了"+t+"小时");Consumer<Integer> consumerChain = eat.andThen(drink).andThen(sport);consumerChain.accept(3);
}
输出
吃了3碗饭
喝了3杯水
运动了3小时
Supplier接口
Supplier 的抽象方法为 get , 没有参数,只有返回值,是一个提供者型接口,lambda 表达式要来表达 get 接口的话,大概长这样
() -> { 逻辑…; return T }
例如下面这个例子
@Test
void testSupplier() {supplierMethod(() -> "张三");supplierMethod(() -> "李四");
}private void supplierMethod(Supplier<String> supplier) {String name = supplier.get();System.out.println(name + "来报到");
}
输出
张三来报到
李四来报到
Function接口
这个接口里的抽象方法是 apply ,接收一个参数 T, 返回一个参数 R。既有输入又有输出。用lambda表达式来表达 apply 方法的话,大概长这样
t -> {逻辑…; return R;}
例如下面这个例子
private static final List<String> nameList = Arrays.asList("张三", "李四", "王五", "刘欢");@Test
void testFunction() {functionMethod(nameList::indexOf);functionMethod(e -> nameList.indexOf(e) + 1);
}private void functionMethod(Function<String, Integer> function) {Integer index = function.apply("王五");System.out.println("王五" + "排在第" + index);
}
输出
王五排在第2
王五排在第3
andThen 方法用于先执行当前函数,再将当前函数的执行结果R作为参数传递给 andThen 的参数中的函数。
例子
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;Function<Integer, Integer> result = multiplyBy2.andThen(add3);System.out.println(result.apply(5)); // 输出:13 (先乘以2,再加3,5*2+3=13)
compose 方法与 andThen 相反,它先执行传递给 compose 的函数,再执行当前函数。
例子
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;Function<Integer, Integer> result = multiplyBy2.compose(add3);System.out.println(result.apply(5)); // 输出:16 (先加3,再乘以2,(5+3)*2=16)
identity 这个静态方法,其实就是 t -> t ; 在stream流式编程中用的比较频繁,比如 list 转 map, value值不变时, 就会用到;下面在 stream 流的用法介绍中也会用到。
Predicate接口
Predicate接口中的抽象方法是 test, 接收一个参数, 返回 boolean 值。用以判定一个对象是不是想要的结果。test方法的 lambda 表达式大概长这样
t -> {逻辑…; return true;}
举个例子
private static final List<String> nameList = Arrays.asList("张三", "李四", "王五", "刘欢");@Test
void testPredicate() {predicateMethod(nameList::contains);predicateMethod(e -> e.length() > 2);
}private void predicateMethod(Predicate<String> predicate) {boolean isStudent = predicate.test("张三");String isStudentCn = isStudent ? "是" : "不是";System.out.println("张三" + isStudentCn + "这个班的学生");
}
输出
张三是这个班的学生
张三不是这个班的学生
接口中其他的集合方法 and , or , negate, isEqual 看一眼就知道是什么意思,不再举例说明了。
JUF包下还有很多接口,大都是这四个接口的扩展,明白了这基本的四个,其它的一看就懂了。
Lambda表达式的基本写法就是 (p1, p2)-> { 逻辑…; return T; }
这是最完整的写法,p1, p2 代表参数1,参数2,并用小括号括起来; 然后是箭头标识 -> , 后面大括号中括着方法体,如果需要返回值的话就要带个 return 语句。
在某些情况下也有简略的写法
- 参数只有一个时,可以去掉小括号:p1 -> { 逻辑…; return T; }
- 方法体只有一条语句时,可以省略大括号:p1 -> return T
- 方法引用。如果有已经存在的方法可以表示这个函数式接口的话,可以直接引用,比如上面用到的 nameList::contains。写法是:前面是对象名(也可以是类名),中间是两个冒号,最后是方法名。 这个用不好的话,你可以直接用最完整的写法去写 lambda, idea会自动提示你有更简略的写法的。
Stream流的用法
stream流也是java8种特别重要的一个特性,它可以使 java 代码像 SQL 一样来处理数据,怎么个用法呢,下面分三点来介绍
- Stream流的创建
- Stream流的中间操作
- Stream流的终止操作
在介绍这三点之前先来点准备数据,方便后面的代码演示
@Data
@AllArgsConstructor
class Student {private String id;private String name;private Integer age;private String grade;private List<String> roles;
}private static final List<Student> stuList = new ArrayList<>();static {stuList.add(new Student("1001", "小明", 12, "一年级", Arrays.asList("班长", "普通学生")));stuList.add(new Student("1002", "小红", 13, "一年级", Arrays.asList("学习委员", "普通学生")));stuList.add(new Student("1003", "李华", 14, "一年级", Arrays.asList("生活委员", "普通学生")));stuList.add(new Student("1004", "丽丽", 15, "二年级", Arrays.asList("普通学生")));stuList.add(new Student("1005", "大黄", 23, "二年级", Arrays.asList("普通学生")));stuList.add(new Student("1006", "小军", 22, "三年级", Arrays.asList("普通学生")));stuList.add(new Student("1007", "小花", 18, "三年级", Arrays.asList("普通学生")));
}
创建一个集合stuList, 里面存放了多个学生对象Student,每个学生有 编号、名字、年龄、年级、班级角色 这几个属性。
Stream流的创建
三种创建方式
1.通过集合创建
Collection 接口中有一个 stream() 方法,像 List 这种实现了 Collection 接口的对象,直接调用 stream() 方法即可。
2.通过 Arrays.stream 方式创建数组形式的stream
int[] aaa = {1, 2, 3};
IntStream stream1 = Arrays.stream(aaa);
3.Stream.of 方法创建
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream流的中间操作
由于stream流的特性,每次操作都必须有个终止操作,在介绍中间操作的这段内容里,终止操作我统一使用 .forEach(System.out::println) 这个操作来打印出结果集合中的内容。
下面来一一列举Stream流中支持的中间操作
filter 过滤
filter(Predicate<? super T> predicate)
// 获取年龄大于18岁的学生
stuList.stream().filter( s -> s.getAge() > 18).forEach(System.out::println);
# 执行结果
Student{id='1005', name='大黄', age=23, grade='二年级', roles=[普通学生]}
Student{id='1006', name='小军', age=22, grade='三年级', roles=[普通学生]}
distinct 去重
该方法是通过 hashCode() 和 equals() 两个方法来判定重复的
// 添加一个重复数据来测试
stuList.add(new Student("1007", "小花", 18, "三年级", Arrays.asList("普通学生")));
stuList.stream().distinct().forEach(System.out::println);
# 执行结果,最终结果只有一个小花
Student{id='1001', name='小明', age=12, grade='一年级', roles=[班长, 普通学生]}
Student{id='1002', name='小红', age=13, grade='一年级', roles=[学习委员, 普通学生]}
Student{id='1003', name='李华', age=14, grade='一年级', roles=[生活委员, 普通学生]}
Student{id='1004', name='丽丽', age=15, grade='二年级', roles=[普通学生]}
Student{id='1005', name='大黄', age=23, grade='二年级', roles=[普通学生]}
Student{id='1006', name='小军', age=22, grade='三年级', roles=[普通学生]}
Student{id='1007', name='小花', age=18, grade='三年级', roles=[普通学生]}
limit 截取返回结果的数量,从头开始
stuList.stream().limit(3).forEach(System.out::println);
# 执行结果
Student{id='1001', name='小明', age=12, grade='一年级', roles=[班长, 普通学生]}
Student{id='1002', name='小红', age=13, grade='一年级', roles=[学习委员, 普通学生]}
Student{id='1003', name='李华', age=14, grade='一年级', roles=[生活委员, 普通学生]}
skip 跳过排在前面的n个值
stuList.stream().skip(3).forEach(System.out::println);
# 执行结果
Student{id='1004', name='丽丽', age=15, grade='二年级', roles=[普通学生]}
Student{id='1005', name='大黄', age=23, grade='二年级', roles=[普通学生]}
Student{id='1006', name='小军', age=22, grade='三年级', roles=[普通学生]}
Student{id='1007', name='小花', age=18, grade='三年级', roles=[普通学生]}
peek 检查元素
peek 主要用于调试,不建议在实际生产逻辑中依赖,常用于打印中间结果
stuList.stream().peek(s -> System.out.println(s.getName())).forEach(System.out::println);
# 执行结果
小明
Student{id='1001', name='小明', age=12, grade='一年级', roles=[班长, 普通学生]}
小红
Student{id='1002', name='小红', age=13, grade='一年级', roles=[学习委员, 普通学生]}
李华
Student{id='1003', name='李华', age=14, grade='一年级', roles=[生活委员, 普通学生]}
丽丽
Student{id='1004', name='丽丽', age=15, grade='二年级', roles=[普通学生]}
大黄
Student{id='1005', name='大黄', age=23, grade='二年级', roles=[普通学生]}
小军
Student{id='1006', name='小军', age=22, grade='三年级', roles=[普通学生]}
小花
Student{id='1007', name='小花', age=18, grade='三年级', roles=[普通学生]}
map 映射
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map接收的参数是一个 function ,有一个输入一个输出,其实就是针对集合中的每一个元素做一个输入输出,输出的内容自由定义;这样的话我们就可以通过 map 方法转换集合中的内容
// 将List<Student> 转换成一个只存储学生名字的 List<String>
stuList.stream().map(Student::getName).forEach(System.out::println);
# 执行结果
小明
小红
李华
丽丽
大黄
小军
小花
mapToInt
同map方法类似,只是将返回类型固定了,所有 List<T> 均处理成 List<Integer>
// 将 List<Student> 转话为只存储年龄的 List<Integer>
stuList.stream().mapToInt(Student::getAge).forEach(System.out::println);
flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
它的主要功能是将每个元素映射成一个流,然后将所有这些流合并成一个单一的流。换句话说,flatMap 可以将嵌套的结构展平,得到一个单层结构,以便处理和分析数据。
flatMap方法的参数是一个Function,且Function的返回结果必须是一个Stream。
// 获取这个学生群体中,一共有多少种角色
stuList.stream().flatMap( s -> s.getRoles().stream()).distinct().forEach(System.out::println);
# 执行结果
班长
普通学生
学习委员
生活委员
sorted 排序
sorted方法有两个,一个无参,一个带参
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
使用无参的 sorted() 方法的话,需要集合中的元素实现了 Comparator 接口才行,否则会报错。
// 将所有学生按照年龄从小到大排序
stuList.stream().sorted((s1,s2) -> Integer.compare(s1.getAge(),s2.getAge())).forEach(System.out::println);// 也可以这样写
stuList.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println);
Stream流的终止操作
下面是stream流终止操作对应的方法,调用了这些方法后,该stream流就终止了。
注意!!!Stream流终止操作返回的对象是一个崭新的对象,不会修改原对象的任何内容。
allMatch 所有元素都匹配
// 判断是否所有学生都大于18岁 (返回 false)
boolean result = stuList.stream().allMatch(s -> s.getAge() > 18);
anyMatch 只要有任意一个元素匹配
// 判断是否有学生大于18岁的 (返回 true)
boolean result = stuList.stream().anyMatch(s -> s.getAge() > 18);
noneMatch 是否没有任何元素匹配
// 判断是不是没有大于18岁的学生 (返回 false)
boolean result = stuList.stream().noneMatch(s -> s.getAge() > 18);
findFirst 返回结果列表中的第一个元素
Optional<Student> first = stuList.stream().findFirst();
System.out.println(first.get());
# 执行结果
Student{id='1001', name='小明', age=12, grade='一年级', roles=[班长, 普通学生]}
findAny 返回结果列表中的任意一个元素
Optional<Student> result = stuList.stream().findAny();
count 计数
// 计算年龄大于18岁的学生有多少个
long count = stuList.stream().filter(s -> s.getAge() > 18).count();
max 最大值
// 获取年龄最大的学生
Optional<Student> max = stuList.stream().max((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));// 也可以这样写
Optional<Student> max = stuList.stream().max(Comparator.comparingInt(Student::getAge));
min 最小值
用法与 max 类似,不再举例
forEach 遍历
void forEach(Consumer<? super T> action);
遍历结果列表中的每个元素,没有返回值
stuList.stream().forEach(s -> {String name = s.getName();Integer age = s.getAge();System.out.println(name + "今天" + age + "岁了");
});
# 执行结果
小明今天12岁了
小红今天13岁了
李华今天14岁了
丽丽今天15岁了
大黄今天23岁了
小军今天22岁了
小花今天18岁了
reduce 规约
reduce用于将流中的元素逐一合并为单一的结果。它通过重复应用一个结合函数(即二元运算)来将流中的元素”归约”为一个值。它非常适用于需要将多个值合并为一个值的场景,例如求和、求积、求最大值等。
Stream API 提供了三个重载的 reduce 方法:
Optional<T> reduce(BinaryOperator<T> accumulator); 单参数,返回一个Optional,代表结果可能为null。
// 计算所有学生的年龄和
Optional<Integer> result = stuList.stream().map(Student::getAge).reduce((age1, age2) -> age1 + age2);
System.out.println(result.get());
T reduce(T identity, BinaryOperator<T> accumulator); 两个参数,相比前一个,多了一个初始化的值,返回结果必定不为空,所以不再是Optional。
// 计算所有学生的年龄和
int result = stuList.stream().mapToInt(Student::getAge).reduce(0, (age1, age2) -> age1 + age2);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); 用于并行流的累加器和组合,新增的这个参数的含义 BinaryOperator<U> combiner:在并行执行时,用于合并不同子流的累积结果。
Integer result = stuList.stream().map(Student::getAge).reduce(0, (age1, age2) -> age1 + age2, (age1, age2) -> age1 + age2);
collect 收集
<R, A> R collect(Collector<? super T, A, R> collector);
这个方法的用法很丰富,它的参数collector通常由 Collectors 的静态方法创建,下面就根据 Collectors 中提供的方法来看看 collect 可以收集到哪些类型的数据。
toList 收集为list
List<Student> adults = stuList.stream().filter(s -> s.getAge() > 18).collect(Collectors.toList());
toMap 收集为map
toMap有三个重载方法;
两个参数的
keyMapper 为键,valueMapper 为值
// 以学生id为键,Student对象为值,将list转为map
Map<String, Student> stuMap = stuList.stream().collect(Collectors.toMap(Student::getId, s -> s));
Map<String, Student> stuMap2 = stuList.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
上面的例子在编程中用的比较多,将list转为map,方便做数据索引。
这个两个参数的方法,在遇到 key 重复时,会报错。
三个参数的
第三个参数就是用来处理 key 重复的问题的,用来合并处理重复的value值
// list 转 map, 名字为key, id为value, 遇到重名的学生就将id用逗号分隔处理
stuList.add(new Student("1008", "小花", 18, "三年级", Arrays.asList("普通学生")));
Map<String, String> stuMergeMap = stuList.stream().collect(Collectors.toMap(Student::getName, Student::getId, (id1, id2) -> id1 + "," + id2));
# 执行结果
{小明=1001, 大黄=1005, 小军=1006, 小红=1002, 小花=1007,1008, 丽丽=1004, 李华=1003}
四个参数的
最后一个参数 mapSupplier 用来指定返回map的类型(默认为HashMap)
// 将返回的map 类型指定为 TreeMap
TreeMap<String, String> stuTreeMap = stuList.stream().collect(Collectors.toMap(Student::getName, Student::getId, (id1, id2) -> id1 + "," + id2, TreeMap::new));
groupingBy 分组
groupingBy 返回一个Map,有三个重载方法
单参数
classifier 定义了分组的键
// 按照年级将所有学生进行分组
Map<String, List<Student>> group1 = stuList.stream().collect(Collectors.groupingBy(Student::getGrade));
# 执行结果
{
"一年级":[{"age":12,"grade":"一年级","id":"1001","name":"小明","roles":["班长","普通学生"]},{"age":13,"grade":"一年级","id":"1002","name":"小红","roles":["学习委员","普通学生"]},{"age":14,"grade":"一年级","id":"1003","name":"李华","roles":["生活委员","普通学生"]}],
"三年级":[{"age":22,"grade":"三年级","id":"1006","name":"小军","roles":["普通学生"]},{"age":18,"grade":"三年级","id":"1007","name":"小花","roles":["普通学生"]}],
"二年级":[{"age":15,"grade":"二年级","id":"1004","name":"丽丽","roles":["普通学生"]},{"age":23,"grade":"二年级","id":"1005","name":"大黄","roles":["普通学生"]}]
}
两个参数
第二个参数 downstream,通过它,你可以将分组的结果收集为各种数据结构(如 List、Set、Map),或对每个分组的元素执行聚合操作(如求和、计数、平均值等)。
// 1.按照年级进行分组,并将每组内的元素改为Map结构
Map<String, Map<String, String>> group2 = stuList.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.toMap(Student::getId, Student::getName)));// 2.统计各年级的人数
Map<String, Long> group3 = stuList.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));// 3.按照年级进行分组,并获取各年级中年龄最大的学生
Map<String, Optional<Student>> group3_2 = stuList.stream().collect(Collectors.groupingBy(Student::getGrade,Collectors.maxBy(Comparator.comparingInt(Student::getAge))));
# 1.执行结果
{
"一年级":{"1003":"李华","1002":"小红","1001":"小明"},
"三年级":{"1007":"小花","1006":"小军"},
"二年级":{"1005":"大黄","1004":"丽丽"}
}# 2. 执行结果
{"一年级":3,"三年级":2,"二年级":2}# 3.执行结果
{
"一年级":{"age":14,"grade":"一年级","id":"1003","name":"李华","roles":["生活委员","普通学生"]},
"三年级":{"age":22,"grade":"三年级","id":"1006","name":"小军","roles":["普通学生"]},
"二年级":{"age":23,"grade":"二年级","id":"1005","name":"大黄","roles":["普通学生"]}
}
三个参数
相比于两个参数的方法,新增的参数 mapFactory ,可以定义返回的Map类型(默认HashMap),例如返回有序的LinkedHashMap(按输入顺序存储分组结果)
// 统计各年级的人数,并将返回结果的格式设置为 LinkedHashMap
LinkedHashMap<String, Long> group4 = stuList.stream().collect(Collectors.groupingBy(Student::getGrade, LinkedHashMap::new, Collectors.counting()));
joining 合并字符串
Collectors.joining() 方法用于将一个字符串集合 合并成一个字符串,并可以设置分隔符。它有三个重载方法
无参方法
直接合并字符串,没有任何分隔符
// 得到所有学生的名字
String joining1 = stuList.stream().map(Student::getName).collect(Collectors.joining());
# 执行结果
小明小红李华丽丽大黄小军小花
单参数
delimiter 是分隔符
String joining2 = stuList.stream().map(Student::getName).collect(Collectors.joining(","));
# 执行结果
小明,小红,李华,丽丽,大黄,小军,小花
两个参数
针对合并后的字符串,可以设置前后缀了, prefix 前缀,suffix 后缀
String joining3 = stuList.stream().map(Student::getName).collect(Collectors.joining(",", "[", "]"));
# 执行结果
[小明,小红,李华,丽丽,大黄,小军,小花]
partitioningBy 分区
Collectors.partitioningBy() 用于将流中的元素按照指定的条件进行分区。它会将元素分成两个组:一个组满足给定的谓词条件,另一个组不满足。返回结果是一个 Map<Boolean, List<T>>,其中 true 键对应的是满足条件的元素列表,false 键对应的是不满足条件的元素列表。
它有两个重载方法
单参数
Predicate 在文章的前半部分提到过,是一个函数式接口,传入一个元素,返回 boolean 值。
// 以18岁为分界线,将学生按照年龄分成两组
Map<Boolean, List<Student>> partition1 = stuList.stream().collect(Collectors.partitioningBy(s -> s.getAge() >= 18));
# 执行结果
{
false:[{"age":12,"grade":"一年级","id":"1001","name":"小明","roles":["班长","普通学生"]},{"age":13,"grade":"一年级","id":"1002","name":"小红","roles":["学习委员","普通学生"]},{"age":14,"grade":"一年级","id":"1003","name":"李华","roles":["生活委员","普通学生"]},{"age":15,"grade":"二年级","id":"1004","name":"丽丽","roles":["普通学生"]}],
true:[{"age":23,"grade":"二年级","id":"1005","name":"大黄","roles":["普通学生"]},{"age":22,"grade":"三年级","id":"1006","name":"小军","roles":["普通学生"]},{"age":18,"grade":"三年级","id":"1007","name":"小花","roles":["普通学生"]}]
}
两个参数
downstream 参数的用法和 groupBy 方法中的用法一样,通过它,你可以将分组的结果收集为各种数据结构(如 List、Set、Map),或对每个分组的元素执行聚合操作(如求和、计数、平均值等)。
// 以18岁为分界线,获取两组学生各有多少人
Map<Boolean, Long> partition2 = stuList.stream().collect(Collectors.partitioningBy(s -> s.getAge() >= 18, Collectors.counting()));
# 执行结果
{false:4,true:3}
collectingAndThen 收集后进一步处理
它允许在执行完某个收集操作后,对收集结果进行进一步的转换操作。通过它,你可以在使用一个基本的收集器后,立即对结果进行处理。
参数释义:
- downstream:一个基础的收集器,用于执行主要的收集操作。
- finisher:在收集操作完成后,对收集结果进行转换的函数。
// 对所有学生进行分组后,再选出每个年级年龄最大的两个学生
Map<String, List<Student>> collectThen = stuList.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.collectingAndThen(Collectors.toList(),list -> list.stream().sorted(Comparator.comparing(Student::getAge).reversed()).limit(2).collect(Collectors.toList()))));
# 执行结果
{
"一年级":[{"age":14,"grade":"一年级","id":"1003","name":"李华","roles":["生活委员","普通学生"]},{"age":13,"grade":"一年级","id":"1002","name":"小红","roles":["学习委员","普通学生"]}],
"三年级":[{"age":22,"grade":"三年级","id":"1006","name":"小军","roles":["普通学生"]},{"age":18,"grade":"三年级","id":"1007","name":"小花","roles":["普通学生"]}],
"二年级":[{"age":23,"grade":"二年级","id":"1005","name":"大黄","roles":["普通学生"]},{"age":15,"grade":"二年级","id":"1004","name":"丽丽","roles":["普通学生"]}]
}
reducing
Collectors.reducing 方法和上面介绍的 reduce 规约 操作的作用和用法是相似的
// 计算所有学生的年龄和
Integer collect = stuList.stream().map(Student::getAge).collect(Collectors.reducing(0,Integer::sum));
Stream流的用法基本也就这些了,接下来就是你在实际编程中活学活用了,加油吧,少年!