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.mzph.cn/diannao/55736.shtml

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

相关文章

【C++】模板(初识):函数模板、类模板

本篇主要介绍C中的模板初阶的一些知识。模板分为函数模板和类模板&#xff0c;我们一个一个来看。 1.函数模板 1.1函数模板概念 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实际的参数类型产生函数特定版本。…

人工智能平台 PAI-DSW内置通义灵码实操

人工智能平台 PAI&#xff08;Platform for AI&#xff0c;原机器学习平台PAI&#xff09;是 AI Native 的大模型与 AIGC 工程平台&#xff0c;提供包含数据集管理、算力管理、模型工具链、模型开发、模型训练、模型部署、AI资产管理在内的功能模块&#xff0c;内置100种大模型…

JVS·智能BI数据可视化图表:普通列表与分组列表配置全解析

使用场景 在可视化配置中&#xff0c;很多场景中需要图形和详细信息的融合展示&#xff0c;那么在图表中可以新增普通列表与分组列表的配置。如下图所示&#xff1a; 配置说明 1、新增组件&#xff1a;配置入口如下图所示&#xff0c;新增组件时&#xff0c;选择普通列表与分…

前端vue-配置请求拦截器

1.配置拦截器&#xff0c;记得20行的导出 2.响应拦截器&#xff0c;记得28行的导出 3.拦截器不止可以拦截&#xff0c;还可以添加内容

Custom C++ and CUDA Extensions - PyTorch

0. Abstract 经历了一波 pybind11 和 CUDA 编程 的学习, 接下来看一看 PyTorch 官方给的 C/CUDA 扩展的教程. 发现极其简单, 就是直接用 setuptools 导出 PyTorch C 版代码的 Python 接口就可以了. 所以, 本博客包含以下内容: LibTorch 初步;C Extension 例子; 1. LibTorch …

CSS3--美若天仙!?

免责声明&#xff1a;本文仅做分享~ 目录 CSS引入方式 选择器 盒子尺寸和背景色 文字控制属性 单行文字 垂直居中 字体族 font复合属性 文本对齐方式 文本修饰线 color 文字颜色 ----- 复合选择器 伪类选择器 超链接伪类 CSS特性 继承性 层叠性 优先级 Emmet …

H、Happy Number(2024牛客国庆集训派对day7)

题目链接&#xff1a; H-Happy Number_2024牛客国庆集训派对day7 (nowcoder.com) 题目描述&#xff1a; 翻译为中文&#xff1a; 数据范围&#xff1a; 输入样例&#xff1a; 680 输出样例&#xff1a; 326623 分析: 本来以为是dfs&#xff0c;但是看到数据范围1e9, 联想到是…

通信工程学习:什么是三网融合

三网融合 三网融合&#xff0c;又称“三网合一”&#xff0c;是指电信网、广播电视网、互联网在高层业务应用上的深度融合。这一概念在近年来随着信息技术的快速发展而逐渐受到重视&#xff0c;并成为推动信息化社会建设的重要力量。以下是对三网融合的详细解释&#xff1a; 一…

扩展、包含、泛化-系统架构师(七十七)

1&#xff08;&#xff09;是系统分析阶段结束后得到的工作产品&#xff0c;&#xff08;&#xff09;是系统测试阶段完成后的工作产品。 问题1 A系统设计规格说明 B系统方案建议书 C系统规格说明 D单元测试数据 问题2 A验收测试计划 B测试标准 C系统测试计划 D操作手…

社团活动助手系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;活动分类管理&#xff0c;用户管理&#xff0c;社团活动管理&#xff0c;报名信息管理&#xff0c;签到登记管理&#xff0c;投票项目管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首…

四款语音转文字神器,一键搞定会议记录!

嘿&#xff0c;朋友们&#xff0c;今天咱们来聊聊那些语音转文字的免费软件吧&#xff01;在这个快节奏的时代&#xff0c;谁不想省点时间&#xff0c;少敲几下键盘呢&#xff1f;尤其是那些开会、采访或者闲聊时&#xff0c;语音消息满天飞的日子&#xff0c;一个好用的语音转…

【业务场景】最全的购物车设计与实现

前言 博主最近在做一个购物商城&#xff0c;正好设计到购物车模块&#xff0c;于是乎全面的来聊一聊购物车模块实现的一些核心要点吧&#xff0c;很值得反复品味的设计&#xff0c;当需要实现购物车的时候&#xff0c;本文应该拿来就能用。 目录 1.需要解决的核心问题清单 2…

Mybatis-plus做了什么

Mybatis-plus做了什么 Mybatis回顾以前的方案Mybatis-plus 合集总览&#xff1a;Mybatis框架梳理 聊一下mybatis-plus。你是否有过疑问&#xff0c;Mybatis-plus中BaseMapper方法对应的SQL在哪里&#xff1f;它为啥会被越来越多人接受。在Mybatis已经足够灵活的情况下&…

22.第二阶段x86游戏实战2-背包遍历REP指令详解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

【Java 并发编程】多线程安全问题(上)

前言 虽然并发编程让我们的 CPU 核心能够得到充分的使用&#xff0c;程序运行效率更高效。但是也会引发一些问题。比如当进程中有多个并发线程进入一个重要数据的代码块时&#xff0c;在修改数据的过程中&#xff0c;很有可能引发线程安全问题&#xff0c;从而造成数据异常。 p…

免费 Oracle 各版本 离线帮助使用和介绍

文章目录 Oracle 各版本 离线帮助使用和介绍概要在线帮助下载离线文档包&#xff1a;解压离线文档&#xff1a;访问离线文档&#xff1a;导航使用&#xff1a;目录介绍Install and Upgrade&#xff08;安装和升级&#xff09;&#xff1a;Administration&#xff08;管理&#…

做无货源反向代购业务需要的代购系统功能需求讲解(一):商品数据接入

在电子商务领域&#xff0c;无货源反向代购业务逐渐崭露头角&#xff0c;成为许多创业者和中小企业拓展市场的新途径。这种业务模式的核心在于通过代购平台&#xff0c;将国外或特定地区的商品信息展示给国内消费者&#xff0c;并在消费者下单后&#xff0c;由代购方进行采购、…

成都睿明智科技有限公司真实可靠吗?

在这个日新月异的电商时代&#xff0c;抖音作为短视频与直播电商的佼佼者&#xff0c;正以前所未有的速度重塑着消费者的购物习惯。而在这片充满机遇与挑战的蓝海中&#xff0c;成都睿明智科技有限公司以其独到的眼光和专业的服务&#xff0c;成为了众多商家信赖的合作伙伴。今…

【万字长文】Word2Vec计算详解(一)

【万字长文】Word2Vec计算详解&#xff08;一&#xff09; 写在前面 本文用于记录本人学习NLP过程中&#xff0c;学习Word2Vec部分时的详细过程&#xff0c;本文与本人写的其他文章一样&#xff0c;旨在给出Word2Vec模型中的详细计算过程&#xff0c;包括每个模块的计算过程&a…

Ubuntu-24.10无法安装Sunlogin-15.2的解决方案

目录 1. 报错信息2. 解决方案3. dpkg-deb命令帮助4. References 1. 报错信息 albertqeeZBG7W:/opt/albertqee/Downloads$ ls | egrep -i sun SunloginClient_11.0.1.44968_amd64.deb SunloginClient_15.2.0.63062_amd64.deb SunloginClient_15.2.0.63064_amd64.deb albertqeeZ…