用了多少年的java8了,Lambda表达式和stream流也经常用,但是也仅限于某些用法比较熟练,看见了 Function、Consumer 等函数式接口还是一脸懵逼,现在来全面总结一下java8这些新特性,也为自己后续查找做个备忘。如果你只是想看某些 method 的用法的话,可以直接通过目录跳转到指定位置。
目录
JUF(java.util.function)包下最基础的四大函数式接口
[filter 过滤](#filter 过滤)
[distinct 去重](#distinct 去重)
[limit 截取返回结果的数量,从头开始](#limit 截取返回结果的数量,从头开始)
[skip 跳过排在前面的n个值](#skip 跳过排在前面的n个值)
[peek 检查元素](#peek 检查元素)
[map 映射](#map 映射)
[sorted 排序](#sorted 排序)
[allMatch 所有元素都匹配](#allMatch 所有元素都匹配)
[anyMatch 只要有任意一个元素匹配](#anyMatch 只要有任意一个元素匹配)
[noneMatch 是否没有任何元素匹配](#noneMatch 是否没有任何元素匹配)
[findFirst 返回结果列表中的第一个元素](#findFirst 返回结果列表中的第一个元素)
[findAny 返回结果列表中的任意一个元素](#findAny 返回结果列表中的任意一个元素)
[count 计数](#count 计数)
[max 最大值](#max 最大值)
[min 最小值](#min 最小值)
[forEach 遍历](#forEach 遍历)
[reduce 规约](#reduce 规约)
[collect 收集](#collect 收集)
[toList 收集为list](#toList 收集为list)
[toMap 收集为map](#toMap 收集为map)
[groupingBy 分组](#groupingBy 分组)
[joining 合并字符串](#joining 合并字符串)
[partitioningBy 分区](#partitioningBy 分区)
[collectingAndThen 收集后进一步处理](#collectingAndThen 收集后进一步处理)
函数式接口与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 这个接口方法的话,大概就长这样
java
t -> {逻辑...}
例如下面这个例子
java
@Test
void testConsumer() {
consumerMethod(t -> System.out.println("小明吃了" + t + "个苹果"));
consumerMethod(t -> System.out.println("小张摘了" + t + "个桃子"));
}
private void consumerMethod(Consumer<Integer> consumer) {
consumer.accept(10);
}
输出结果:
bash
小明吃了10个苹果
小张摘了10个桃子
另外Consumer接口中还有一个 andThen 方法,通过这个方法可以将一堆 Consumer 串起来,组成一个链式消费链路。
例子
java
@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);
}
输出
bash
吃了3碗饭
喝了3杯水
运动了3小时
Supplier接口
Supplier 的抽象方法为 get , 没有参数,只有返回值,是一个提供者型接口,lambda 表达式要来表达 get 接口的话,大概长这样
java
() -> { 逻辑...; return T }
例如下面这个例子
java
@Test
void testSupplier() {
supplierMethod(() -> "张三");
supplierMethod(() -> "李四");
}
private void supplierMethod(Supplier<String> supplier) {
String name = supplier.get();
System.out.println(name + "来报到");
}
输出
bash
张三来报到
李四来报到
Function接口
这个接口里的抽象方法是 apply ,接收一个参数 T, 返回一个参数 R。既有输入又有输出。用lambda表达式来表达 apply 方法的话,大概长这样
java
t -> {逻辑...; return R;}
例如下面这个例子
java
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);
}
输出
bash
王五排在第2
王五排在第3
andThen 方法用于先执行当前函数,再将当前函数的执行结果R作为参数传递给 andThen 的参数中的函数。
例子
java
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 的函数,再执行当前函数。
例子
java
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 表达式大概长这样
java
t -> {逻辑...; return true;}
举个例子
java
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 + "这个班的学生");
}
输出
java
张三是这个班的学生
张三不是这个班的学生
接口中其他的集合方法 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流的终止操作
在介绍这三点之前先来点准备数据,方便后面的代码演示
java
@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
java
int[] aaa = {1, 2, 3};
IntStream stream1 = Arrays.stream(aaa);
3.Stream.of 方法创建
java
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream流的中间操作
由于stream流的特性,每次操作都必须有个终止操作,在介绍中间操作的这段内容里,终止操作我统一使用 .forEach(System.out::println) 这个操作来打印出结果集合中的内容。
下面来一一列举Stream流中支持的中间操作
filter 过滤
filter(Predicate<? super T> predicate)
java
// 获取年龄大于18岁的学生
stuList.stream().filter( s -> s.getAge() > 18).forEach(System.out::println);
bash
# 执行结果
Student{id='1005', name='大黄', age=23, grade='二年级', roles=[普通学生]}
Student{id='1006', name='小军', age=22, grade='三年级', roles=[普通学生]}
distinct 去重
该方法是通过 hashCode() 和 equals() 两个方法来判定重复的
java
// 添加一个重复数据来测试
stuList.add(new Student("1007", "小花", 18, "三年级", Arrays.asList("普通学生")));
stuList.stream().distinct().forEach(System.out::println);
bash
# 执行结果,最终结果只有一个小花
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 截取返回结果的数量,从头开始
java
stuList.stream().limit(3).forEach(System.out::println);
bash
# 执行结果
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个值
java
stuList.stream().skip(3).forEach(System.out::println);
bash
# 执行结果
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 主要用于调试,不建议在实际生产逻辑中依赖,常用于打印中间结果
java
stuList.stream().peek(s -> System.out.println(s.getName())).forEach(System.out::println);
bash
# 执行结果
小明
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 方法转换集合中的内容
java
// 将List<Student> 转换成一个只存储学生名字的 List<String>
stuList.stream().map(Student::getName).forEach(System.out::println);
bash
# 执行结果
小明
小红
李华
丽丽
大黄
小军
小花
mapToInt
同map方法类似,只是将返回类型固定了,所有 List<T> 均处理成 List<Integer>
java
// 将 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。
java
// 获取这个学生群体中,一共有多少种角色
stuList.stream().flatMap( s -> s.getRoles().stream()).distinct().forEach(System.out::println);
bash
# 执行结果
班长
普通学生
学习委员
生活委员
sorted 排序
sorted方法有两个,一个无参,一个带参
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
使用无参的 sorted() 方法的话,需要集合中的元素实现了 Comparator 接口才行,否则会报错。
bash
// 将所有学生按照年龄从小到大排序
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 所有元素都匹配
java
// 判断是否所有学生都大于18岁 (返回 false)
boolean result = stuList.stream().allMatch(s -> s.getAge() > 18);
anyMatch 只要有任意一个元素匹配
java
// 判断是否有学生大于18岁的 (返回 true)
boolean result = stuList.stream().anyMatch(s -> s.getAge() > 18);
noneMatch 是否没有任何元素匹配
java
// 判断是不是没有大于18岁的学生 (返回 false)
boolean result = stuList.stream().noneMatch(s -> s.getAge() > 18);
findFirst 返回结果列表中的第一个元素
java
Optional<Student> first = stuList.stream().findFirst();
System.out.println(first.get());
bash
# 执行结果
Student{id='1001', name='小明', age=12, grade='一年级', roles=[班长, 普通学生]}
findAny 返回结果列表中的任意一个元素
java
Optional<Student> result = stuList.stream().findAny();
count 计数
java
// 计算年龄大于18岁的学生有多少个
long count = stuList.stream().filter(s -> s.getAge() > 18).count();
max 最大值
java
// 获取年龄最大的学生
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);
遍历结果列表中的每个元素,没有返回值
java
stuList.stream().forEach(s -> {
String name = s.getName();
Integer age = s.getAge();
System.out.println(name + "今天" + age + "岁了");
});
bash
# 执行结果
小明今天12岁了
小红今天13岁了
李华今天14岁了
丽丽今天15岁了
大黄今天23岁了
小军今天22岁了
小花今天18岁了
reduce 规约
reduce用于将流中的元素逐一合并为单一的结果。它通过重复应用一个结合函数(即二元运算)来将流中的元素"归约"为一个值。它非常适用于需要将多个值合并为一个值的场景,例如求和、求积、求最大值等。
Stream API 提供了三个重载的 reduce 方法:
**Optional<T> reduce(BinaryOperator<T> accumulator);**单参数,返回一个Optional,代表结果可能为null。
java
// 计算所有学生的年龄和
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。
java
// 计算所有学生的年龄和
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:在并行执行时,用于合并不同子流的累积结果。
java
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
java
List<Student> adults = stuList.stream().filter(s -> s.getAge() > 18).collect(Collectors.toList());
toMap 收集为map
toMap有三个重载方法;
两个参数的
keyMapper 为键,valueMapper 为值
java
// 以学生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值
java
// 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));
bash
# 执行结果
{小明=1001, 大黄=1005, 小军=1006, 小红=1002, 小花=1007,1008, 丽丽=1004, 李华=1003}
四个参数的
最后一个参数 mapSupplier 用来指定返回map的类型(默认为HashMap)
java
// 将返回的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 定义了分组的键
java
// 按照年级将所有学生进行分组
Map<String, List<Student>> group1 = stuList.stream().collect(Collectors.groupingBy(Student::getGrade));
bash
# 执行结果
{
"一年级":[{"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),或对每个分组的元素执行聚合操作(如求和、计数、平均值等)。
java
// 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))
));
bash
# 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(按输入顺序存储分组结果)
java
// 统计各年级的人数,并将返回结果的格式设置为 LinkedHashMap
LinkedHashMap<String, Long> group4 = stuList.stream()
.collect(Collectors.groupingBy(Student::getGrade, LinkedHashMap::new, Collectors.counting()));
joining 合并字符串
Collectors.joining() 方法用于将一个字符串集合 合并成一个字符串,并可以设置分隔符。它有三个重载方法
无参方法
直接合并字符串,没有任何分隔符
java
// 得到所有学生的名字
String joining1 = stuList.stream().map(Student::getName).collect(Collectors.joining());
bash
# 执行结果
小明小红李华丽丽大黄小军小花
单参数
delimiter 是分隔符
java
String joining2 = stuList.stream().map(Student::getName).collect(Collectors.joining(","));
bash
# 执行结果
小明,小红,李华,丽丽,大黄,小军,小花
两个参数
针对合并后的字符串,可以设置前后缀了, prefix 前缀,suffix 后缀
java
String joining3 = stuList.stream().map(Student::getName).collect(Collectors.joining(",", "[", "]"));
bash
# 执行结果
[小明,小红,李华,丽丽,大黄,小军,小花]
partitioningBy 分区
Collectors.partitioningBy() 用于将流中的元素按照指定的条件进行分区。它会将元素分成两个组:一个组满足给定的谓词条件,另一个组不满足。返回结果是一个 Map<Boolean, List<T>>,其中 true 键对应的是满足条件的元素列表,false 键对应的是不满足条件的元素列表。
它有两个重载方法
单参数
Predicate 在文章的前半部分提到过,是一个函数式接口,传入一个元素,返回 boolean 值。
java
// 以18岁为分界线,将学生按照年龄分成两组
Map<Boolean, List<Student>> partition1 = stuList.stream()
.collect(Collectors.partitioningBy(s -> s.getAge() >= 18));
bash
# 执行结果
{
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),或对每个分组的元素执行聚合操作(如求和、计数、平均值等)。
java
// 以18岁为分界线,获取两组学生各有多少人
Map<Boolean, Long> partition2 = stuList.stream()
.collect(Collectors.partitioningBy(s -> s.getAge() >= 18, Collectors.counting()));
bash
# 执行结果
{false:4,true:3}
collectingAndThen 收集后进一步处理
它允许在执行完某个收集操作后,对收集结果进行进一步的转换操作。通过它,你可以在使用一个基本的收集器后,立即对结果进行处理。
参数释义:
- downstream:一个基础的收集器,用于执行主要的收集操作。
- finisher:在收集操作完成后,对收集结果进行转换的函数。
java
// 对所有学生进行分组后,再选出每个年级年龄最大的两个学生
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())
)));
bash
# 执行结果
{
"一年级":[{"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 规约操作的作用和用法是相似的
java
// 计算所有学生的年龄和
Integer collect = stuList.stream().map(Student::getAge).collect(Collectors.reducing(0,Integer::sum));
Stream流的用法基本也就这些了,接下来就是你在实际编程中活学活用了,加油吧,少年!