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

news2024/10/9 16:47:10

用了多少年的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.coloradmin.cn/o/2199514.html

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

相关文章

体系指南|如何从0到1建设一套软件测试CMA体系

软件测试实验室在申请CMA测试认证时&#xff0c;需要根据CMA的要求&#xff0c;建立一套质量管理体系&#xff0c;还需要参照相关标准建立一套技术体系。本文我们重点介绍质量管理体系的建设。这部分内容我们可以参照《检验检测机构资质认定能力评价 检验检测机构通用要求》中&…

Python神仙级思维导图+入门教程(非常详细,入门从这篇开始)

入门 Python 绝非难事&#xff0c;但如何让自己坚持学下去是如今很多学习者面对的一大难题。为了避免像背单词永远停留在 abandon 一样&#xff0c;积极展开自救的小编在尝试过一些入门方法后&#xff0c;终于找到了一个超级棒的一份思维导图视频教程 这是我刚开始学习python时…

谷歌发布建筑数据,高度误差达惊人的1.5米

建筑数据对规划人口增长、应对危机和深入了解城市化带来的影响有很大的帮助&#xff0c;这里为大家分享谷歌全球南方带高度的建筑数据集。 数据介绍 到2050年&#xff0c;全球城市人口预计将增加25亿&#xff0c;其中近90%的增幅将出现在亚洲和非洲的城市。 但恰巧是这些地方…

World of Warcraft [WeakAuras](WA)

https://addons.wago.io/addons/weakauras WeakAuras-5.13.1 复制到自己游戏的AddOns目录 D:\Battle.net\World of Warcraft\_classic_\Interface\AddOns 启动命令 /wa

职场基本功:击退欺凌

文章目录 引言I 什么是职场欺凌?不友好行动确认对方是不是真的有敌意II 反击欺凌信任领导找到敌营里的薄弱点,击溃打手,各个击破。别急着跟欺凌者和解III 应对特殊情况的欺凌引言 面对职场欺凌,有明确敌意的。这时候需要你反击,千万别认怂。 但不是所有的欺凌行为都值得我…

HTML5实现古典音乐网站源码模板1

文章目录 1.设计来源1.1 网站首页1.2 古典音乐界面1.3 著名人物界面1.4 古典乐器界面1.5 历史起源界面2.效果和源码2.1 动态效果2.2 源代码源码下载万套模板,程序开发,在线开发,在线沟通作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/142…

一键升级 Win10 22H2 19045.5011 正式版:含9项改进和修复!

今日&#xff0c;系统之家小编给您分享2024年10月最新更新的Windows10 22H2正式版系统下载&#xff0c;该版本系统基于微软官方最新Windows10 22H2 19045.5011 64位专业版进行离线制作&#xff0c;安全无毒&#xff0c;集成万能驱动&#xff0c;且本次更新包含了9项改进和修复&…

粤港澳脑病中医药科创大会召开,助力脑病相关中药研发、转化

9月28日&#xff0c;由广州粤港澳脑病中医药产学研技术创新联盟、广州市脑病中医药大健康产学研促进会与暨南大学联合主办&#xff0c;暨南大学中医学院及中医药调控脑-外周稳态与大健康粤港澳联合实验室承办&#xff0c;中国生理学会中医药与脑稳态调控专委会、生物活性分子与…

【含开题报告+文档+PPT+源码】基于springboot的迎新系统

开题报告 大学迎新系统是为了满足大学在新生入学时的信息化处理需求而开发的系统。在传统方式下&#xff0c;我们新生接待工作是需要新生报名表&#xff0c;就使得我们需要耗费大量的纸张&#xff0c;这将造成资源浪费。在接待新生的时候需要让新生勾选、填写大量的表格&#…

电源管理芯片PMIC

一、简介 电源管理芯片&#xff08;Power Management Integrated Circuits&#xff0c;简称PMIC&#xff09;是一种集成电路&#xff0c;它的主要功能是在电子设备系统中对电能进行管理和控制&#xff0c;包括但不限于以下几点&#xff1a; 电压转换&#xff1a;将电源电压转换…

软件工程师必备技能:掌握Postman接口自动化测试

Postman 是一款功能强大的API开发工具&#xff0c;它允许用户轻松地发送HTTP请求&#xff0c;管理、测试和记录APIs。在当今的软件开发中&#xff0c;API的测试是一个至关重要的环节&#xff0c;因为它确保了不同软件模块之间的正确交互。Postman的接口自动化测试功能&#xff…

ultralytics yolo segmentation 分割 示例:加载官方segmentation 模型进行推理

Ultralytics YOLO 是计算机视觉和 ML 领域专业人士的高效工具。 安装 ultralytics 库&#xff1a; pip install ultralytics 该示例的模型地址&#xff1a;ultralyticsyolosegmentation分割模型资源-CSDN文库 大家也可以在官方连接下载&#xff0c;因为有时下载容易断开链…

【python实操】python小程序之魔法方法(__init__方法、__str__方法、__del__方法)

引言 python小程序之魔法方法&#xff08;__init__方法、__str__方法、__del__方法&#xff09; 文章目录 引言一、__init__方法1.1 题目1.2 代码1.3 代码解释1.3.1 逐行注释1.3.2 代码执行过程 二、__str__方法2.1 题目2.2 代码2.3 代码解释 三、__del__方法3.1 题目3.2 代码3…

C# HttpClient请求URL重定向后丢失Authorization认证头信息 .Net Core Web Api

问题: 使用.Net 入库Doris请求FE端口后,FE响应重定向到其他BE节点出现的认证失败问题。 搜查官方文档后发现&#xff1a; HttpWebRequest.AllowAutoRedirect Property (System.Net) | Microsoft Learn 微软提供的http类库HttpClient &#xff08;HttpWebRequest\WebClient已不…

springboot校园交友平台

基于springbootvue实现的校园交友平台 &#xff08;源码L文ppt&#xff09;4-082 第四章 系统设计 4.1 系统总体设计 系统的建设可以为校园交友管理提供帮助&#xff0c;通过对一些基础信息管理实现针对性的安排&#xff0c;可以按照用户的角色权限使不同用户角色看…

鲁大师2024年电动车Q3季报:九号E150 MK2刷新榜单,小牛极核发力智能驾辅赛道

鲁大师2024年Q3季报正式发布&#xff0c;本次季报包含电动车智能排行&#xff0c;测试的车型为市面上主流品牌的主流车型&#xff0c;共计18款&#xff0c;全部按照评测维度更广、更专业的鲁大师电动车智慧评测2.0进行评分&#xff0c;测试的成绩均来自于鲁大师智慧硬件实验室。…

ai美女一天涨粉过万日入过千?

我们先看看现阶段的美女视频已经可以达到系统无法识别ai的程度了 1、ai生成高拟真美女图片 我们打开鲸歌ai助手&#xff0c;选择ai绘画&#xff0c;输入以下提示词&#xff1a;一个亚洲面孔美女网红&#xff0c;白色T恤&#xff0c;牛仔短裙&#xff0c;手机相片&#xff0c;…

YOLO11涨点优化:注意力魔改 | 动态稀疏注意力的双层路由方法BiLevelRoutingAttention | CVPR2023

💡💡💡本文改进内容: BiLevelRoutingAttention方法对小目标检测效果比较好。可能是因为BRA模块是基于稀疏采样而不是下采样,一来可以保留细粒度的细节信息,二来同样可以达到节省计算量的目的。 💡💡💡本文改进:分别加入到YOLO11的backbone、neck、detect,助力…

ggshield:查找并修复基础设施即代码错误配置和硬编码密钥

关于ggshield ggshield是一款针对基础设施及代码的安全检测工具&#xff0c;该工具支持查找并修复 400 多种类型的硬编码敏感数据和 70 多种类型的基础设施即代码配置错误。 ggshield是一个在你的本地环境或 CI 环境中运行的 CLI 应用程序&#xff0c;可帮助你检测 400 多种类…

【JAVA开源】基于Vue和SpringBoot的知识管理系统

本文项目编号 T 074 &#xff0c;文末自助获取源码 \color{red}{T074&#xff0c;文末自助获取源码} T074&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…