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

用了多少年的java8了,Lambda表达式和stream流也经常用,但是也仅限于某些用法比较熟练,看见了 Function、Consumer 等函数式接口还是一脸懵逼,现在来全面总结一下java8这些新特性,也为自己后续查找做个备忘。如果你只是想看某些 method 的用法的话,可以直接通过目录跳转到指定位置。

目录

函数式接口与Lambda表达式

JUF(java.util.function)包下最基础的四大函数式接口

Consumer接口

Supplier接口

Function接口

Predicate接口

Stream流的用法

Stream流的创建

Stream流的中间操作

[filter 过滤](#filter 过滤)

[distinct 去重](#distinct 去重)

[limit 截取返回结果的数量,从头开始](#limit 截取返回结果的数量,从头开始)

[skip 跳过排在前面的n个值](#skip 跳过排在前面的n个值)

[peek 检查元素](#peek 检查元素)

[map 映射](#map 映射)

mapToInt

flatMap

[sorted 排序](#sorted 排序)

Stream流的终止操作

[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 收集后进一步处理)

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 这个接口方法的话,大概就长这样

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 一样来处理数据,怎么个用法呢,下面分三点来介绍

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

相关推荐
阿龟在奔跑1 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
飞滕人生TYF1 小时前
m个数 生成n个数的所有组合 详解
java·递归
代码小鑫1 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖2 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
激流丶2 小时前
【Kafka 实战】Kafka 如何保证消息的顺序性?
java·后端·kafka
周全全2 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
uzong3 小时前
一个 IDEA 老鸟的 DEBUG 私货之多线程调试
java·后端
AiFlutter3 小时前
Java实现简单的搜索引擎
java·搜索引擎·mybatis
飞升不如收破烂~3 小时前
Spring boot常用注解和作用
java·spring boot·后端
秦老师Q3 小时前
Java基础第九章-Java集合框架(超详细)!!!
java·开发语言