Java8新特性, 函数式编程及Stream流用法大全

用了多少年的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)包下最基础的四大函数式接口

  1. Consumer<T> 
  2. Supplier<T>
  3. Function<T, R>
  4. 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 一样来处理数据,怎么个用法呢,下面分三点来介绍

  1. Stream流的创建
  2. Stream流的中间操作
  3. 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流的用法基本也就这些了,接下来就是你在实际编程中活学活用了,加油吧,少年!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/442654.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

启用vnc访问Dell 服务器IDRAC 7虚拟控制台

Dell IDRAC 7 版本太老&#xff0c;SSL证书过期&#xff0c;IDRAC的Java和本地远程虚拟机控制台访问不了&#xff0c;怎么办&#xff1f; 可以启用vnc访问IDRAC 虚拟控制台

解决雪花ID在前端精度丢失问题

解决雪花ID在前端精度丢失问题 在现代分布式系统中&#xff0c;雪花算法&#xff08;Snowflake&#xff09;被广泛用于生成唯一的ID。这些ID通常是Long类型的整数。然而&#xff0c;当这些ID从后端传递到前端时&#xff0c;JavaScript的精度限制可能会导致精度丢失&#xff0c…

Leetcode: 0021-0030题速览

Leetcode: 0021-0030题速览 本文材料来自于LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer&#xff08;第 2 版&#xff09;》、《程序员面试金典&#xff08;第 6 版&#xff09;》题解 遵从开源协议为知识共享 版权归属-相同方式…

云手机哪款好用?2024年云手机推荐对比指南

随着云手机市场的快速扩展&#xff0c;消费者在选择云手机时面临着众多选择。为了帮助大家找到最适合自己的云手机&#xff0c;小编特意整理了一份当前市场上几款备受关注的云手机品牌对比&#xff0c;大家一起往下看吧。 1. Ogphone云手机 Ogphone云手机是近年来海外业务版块迅…

PCIe配置篇(2)——如何进行配置操作(二)

一、配置机制 我们之前提到过&#xff0c;配置空间存在于PCIe设备上&#xff0c;而处理器通常无法直接执行配置读写请求&#xff0c;因为它只能生成内存和I/O请求。这意味着RC&#xff08;Root Complex&#xff09;需要将某些访问请求转换为配置请求&#xff0c;以支持配置空间…

SpringBoot3响应式编程全套-Spring Webflux

目录 传送门前言一、组件对比二、WebFlux1、引入2、Reactor Core3、DispatcherHandler3.1、请求处理流程 4、注解开发4.1、目标方法传参4.2、返回值写法 5、文件上传6、错误处理7、RequestContext8、自定义Flux配置9、Filter 传送门 SpringMVC的源码解析&#xff08;精品&…

python操作.docx、.pptx文件

python操作.docx、.pptx文件 .docx文件和.pptx文件是Microsoft Office套件中两种常见的文件格式&#xff0c;分别对应Word文档和PowerPoint演示文稿。WPS Office完美支持Microsoft Office文件格式。 使用 Python 操作 .docx 和 .pptx 文件是一项非常实用的技能&#xff0c;尤…

ElasticSearch 备考 -- Snapshot Restore

一、题目 备份集群下的索引 task&#xff0c;存储快照名称为 snapshot_1 二、思考 这个涉及的是集群的备份&#xff0c;主要是通过创建快照&#xff0c;涉及到以下2步骤 Setp1&#xff1a;注册一个备份 snapshot repository Setp2&#xff1a;创建 snapshot 可以通过两种方…

目标检测 DN-DETR(2022)

文章目录 前言gt labels 和gt boxes加噪query的构造attention maskIS&#xff08;InStability&#xff09;指标 前言 gt labels 和gt boxes加噪 query的构造 attention mask IS&#xff08;InStability&#xff09;指标

工行企业网银U盾展期后有两个证书问题的解决方法

工行企业网银U盾证书快到期后&#xff0c;可以自助展期&#xff0c;流程可以根据企业网银提示页面操作。操作后&#xff0c;可能存在两个新旧两个证书并存的情况&#xff0c;致使网银转账等操作失败&#xff0c;如图&#xff1a; 其原因是新证书生成后&#xff0c;旧证书没有删…

TCP_SOCKET编程实现

文章目录 与UDP_SOCKET的区别第一代Tcp_ServerTcp_Client第二代Tcp_Server第三代Tcp_server多线程版本Tcp_Server线程池版的Tcp_Server使用inet_ntop来解决线程安全问题 业务逻辑编写总结补充说明&&业务代码完成ping的真实作用Translate编写Transform业务代码 整体总结…

胤娲科技:机械臂「叛逃」记——自由游走,再悄然合体

夜深人静&#xff0c;你正沉浸在梦乡的前奏&#xff0c;突然意识到房间的灯还亮着。此刻的你&#xff0c;是否幻想过有一只无形的手&#xff0c;轻盈地飘过&#xff0c;帮你熄灭那盏碍眼的灯&#xff1f; 又或者&#xff0c;你正窝在沙发上&#xff0c;享受电视剧的紧张刺激&am…

C语言 | Leetcode C语言题解之第466题统计重复个数

题目&#xff1a; 题解&#xff1a; #include <stdlib.h> #include <stdio.h> #include <stdbool.h> #include <string.h> #include <math.h> #include <limits.h>#define MMAX(a, b) ((a) > (b)? (a) : (b)) #define MMIN(a,…

打卡第六天 P10287 [GESP样题 七级] 最长不下降子序列

今天是我打卡第六天&#xff0c;做个普及/提高−题吧(#^.^#) 原题链接&#xff1a;[GESP样题 七级] 最长不下降子序列 - 洛谷 题目描述 输入格式 输出格式 输出一行一个整数表示答案。 输入输出样例 输入 #1 5 4 2 10 6 3 1 5 2 2 3 3 1 1 4 输出 #1 3 输入 #2 6 11 …

微服务——分布式事务

目录 分布式事务 1.1分布式事务的特性 1.2分布式事务应用背景 ​编辑 1.3.认识Seata 1.4部署TC服务 1.4.1.准备数据库表 1.4.2.准备配置文件 1.4.3.Docker部署 1.5.微服务集成Seata 1.5.1.引入依赖 1.5.2.改造配置 1.5.3.添加数据库表 ​编辑1.6.XA模式 1.6.1.两…

JavaScript函数基础(通俗易懂篇)

10.函数 10.1 函数的基础知识 为什么会有函数&#xff1f; 在写代码的时候&#xff0c;有一些常用的代码需要书写很多次&#xff0c;如果直接复制粘贴的话&#xff0c;会造成大量的代码冗余&#xff1b; 函数可以封装一段重复的javascript代码&#xff0c;它只需要声明一次&a…

精品WordPress主题/响应式个人博客主题Kratos

Kratos 是一款专注于用户阅读体验的响应式 WordPress 主题&#xff0c;整体布局简洁大方&#xff0c;针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净&#xff0c;简单且响应迅速的博客主题&#xff0c;Vtrois创建和维护&#xff0c; 主…

LeetCode 刷题基础 -- 模板原型Ⅰ

模板原型 - 基础篇 学习网站一、进制转换二、二分查找① 查找指定元素② 查找第一个大于等于 x 值的序列下标③ 查找第一个大于 x 值的序列下标④ 单峰序列 三、双指针① 两数之和② 序列合并③ 集合求交④ 集合求并 四、其他高效技巧与算法① 区间和② 01 对③ 左小数 五、数学…

BLE MESH学习1-基于沁恒CH582学习

BLE MESH学习1-基于沁恒CH582学习 一、BLE mesh说明 mesh组网可以实现相比点对点模式更远的距离、更灵活的网络形式、更可靠的连接和更多的设备加入。BLE mesh在IoT中的传感器和控制具有重要意义。我的目的也是IoT领域&#xff0c;实现自己的传感器读取、开关控制等类似米家智…

知识改变命运 数据结构【java对象的比较】

0&#xff1a;前言 在基本数据类型中&#xff0c;我们可以直接使用号比较是否相等&#xff0c;还记的学堆哪里时候&#xff0c;插入一个数据&#xff0c;就会与其他数据进行比较&#xff0c;当时我们传入的是Integer类型&#xff0c;在Integer类里面已经实现了compare。 如果…