【Java】`Stream流`的终结方法

概念:终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作

一、总述

常见的终结方法会有以下四个

方法名说明
void forEach(Consumer action)遍历
long count()统计
toArray()收集流中的数据,放到数组中
collect(Collector collector)收集流中的数据,放到集合中

思考为什么要收集?

我们使用 Stream流 可以用来处理 集合 / 数组 中的数据,那你处理完了之后,肯定要将这些数据保存起来,因此我们需要将它们收集起来。


二、遍历

void forEach(Consumer action)

代码示例

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");

//用list调用stream()获取到一个流水线,并把集合里面的数据放到流水线上
list.stream()

可以发现 forEach() 的返回值是 void,因此调用 forEach() 结束后就不能调用流里面的其他方法了,它是终结方法。

image-20240430153920243

跟进 forEach() ,可以发现它的形参是 Consumer,继续跟进 Consumer

发现它是一个函数式接口,因此一会我们会将它改写成 Lambda表达式

image-20240430153734914

我们先写匿名内部类

//Consumer的泛型:表示流中数据的类型
//accept方法的形参s:依次表示流里面的每一个数据
//方法体:对每一个数据的处理操作(打印)
list.stream().forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});

改写为 Lambda表达式

list.stream().forEach(s -> System.out.println(s));

三、统计

long count()

count() 的返回值是一个 long类型 的整数,因此 count() 结束后,也不能再调用流里面的其他方法了,它也是一个终结方法。

image-20240430154059261
long count = list.stream().count();
System.out.println(count); // 9

四、收集流中的数据,放到数组中

toArray()

调用 toArray() 的时候会有两个,最简单的是空参的,表示我们需要收集到 Object类型 的数组中。

但是我们需要的是放到具体类型的数组中,就可以使用下面的方法。

image-20240430154335652

首先来讲最简单的空参,返回的是 Object类型 的数组中。

Object[] arr1 = list.stream().toArray();
System.out.println(Arrays.toString(arr1));

接下来讲有参的,跟进 toArray() 往下滑

image-20240430154757072

点击 IntFunction,可以发现它也是一个函数式接口

image-20240430154841984

这里先写匿名内部类

list.stream().toArray(new IntFunction<? extends Object[]>() {
    @Override
    public Object[] apply(int value) {
        return new Object[0];
    }
});

这个接口相对来将比较复杂。

IntFunction 的泛型是 ? extends Object[],前面的 ? extends Object 表示数据的类型,后面的 [] 表示数组,因此这个整体就表示具体类型的数组。

由于我们要的是 String 类型的,因此写 String[] 就行了。

list.stream().toArray(new IntFunction<String[]>() {
    @Override
    public Object[] apply(int value) {
        return new Object[0];
    }
});

接下来看里面的 apply()

apply的形参:流中数据的个数,要跟数组的长度保持一致
apply的返回值:具体类型的数组,这个需要跟IntFunction的泛型类型对应
方法体:就是创建数组
toArray方法的参数的作用:负责创建一个指定类型的数组
toArray方法的底层,会依次得到流里面的每一个数据,并把数据放到数组当中
toArray方法的返回值:是一个装着流里面所有数据的数组
String[] arr = list.stream().toArray(new IntFunction<String[]>() {
    @Override
    public String[] apply(int value) {
        return new String[value];
    }
});
System.out.println(Arrays.toString(arr)); // [张无忌, 周芷若, 赵敏, 张强, 张三丰, 张翠山, 张良, 王二麻子, 谢广坤]

改写成 Lambda表达式

String[] arr2 = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(arr2)); // [张无忌, 周芷若, 赵敏, 张强, 张三丰, 张翠山, 张良, 王二麻子, 谢广坤]

PS:如果需要转为基本数据类型的数组,泛型是不能写基本数据类型的,只能转为它的包装类

image-20240501095451388
list.stream().toArray(new IntFunction<Integer[]>() {
    @Override
    public Integer[] apply(int value) {
        return new Integer[0];
    }
});

五、收集流中的数据,放到集合中

这个方法可以收集到 单列集合List / Set双列集合Map

collect(Collector collector)

准备数据

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
        "张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");

1)收集到 List集合 当中

需求:我要把所有的男性收集起来

PS:我们在比较的时候也可以将 s.split("-")[1] 放在前面跟 "男" 进行比较也是可以的,只不过我们在比较的时候有一个习惯:能用这种固定数据去调用的尽量就使用固定数据调用,因为前面的不可能是 null,但是后面的 s.split("-")[1] 就有可能是不确定的,不确定的东西就有可能是 nullnull 的话就会导致空指针异常。

List<String> newList1 = list.stream()
    .filter(s -> "男".equals(s.split("-")[1]))
    // Collectors是Stream里面的工具类,它里面有个静态方法叫:tolist(),这个方法底层可以帮我们创建一个ArrayList集合,这样就可以将流里面所有的数据都放到这个ArrayList集合中了
    .collect(Collectors.toList());
System.out.println(newList1); // [张无忌-男-15, 张强-男-20, 张三丰-男-100, 张翠山-男-40, 张良-男-35, 王二麻子-男-37, 谢广坤-男-41]

2)收集到 Set集合 当中

Set<String> newList2 = list.stream().filter(s -> "男".equals(s.split("-")[1]))
    // toSet() 底层会帮我们创建一个HashSet集合,并将流里面的数据放到这个HashSet集合中
    .collect(Collectors.toSet());
System.out.println(newList2);

3)收集到 List集合Set集合 中的区别

当我将 list 集合中的数据改为

"张无忌-男-15", "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
        "张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41"

此时数据就重复了,这时我们将 newList1newList2 同时打印。

可以发现如果将数据收集到 HashSet 中,数据会去重。

System.out.println(newList1); // [张无忌-男-15, 张无忌-男-15, 张强-男-20, 张三丰-男-100, 张翠山-男-40, 张良-男-35, 王二麻子-男-37, 谢广坤-男-41]
System.out.println(newList2); // [张强-男-20, 张良-男-35, 张三丰-男-100, 张无忌-男-15, 谢广坤-男-41, 张翠山-男-40, 王二麻子-男-37]

4)收集到 Map集合 当中

收集到 Map 时,需要规定一件事情:谁作为键,谁作为值。

需求:
我要把所有的男性收集起来
键:姓名。 值:年龄。
性别就不要了,因为既然是将所有的男性收集起来,那么收集起来的肯定是男性。

toMap() 中,我们需要传入键和值的规则

//收集Map集合当中
//谁作为键,谁作为值.
//我要把所有的男性收集起来
//键:姓名。 值:年龄
Map<String, Integer> map = list.stream()
    .filter(s -> "男".equals(s.split("-")[1]))
    .collect(键的规则, 值的规则);

跟进 toMap() 看一下,可以发现集合中有两个形参,形参一:keyMapper(键的规则),形参二:valueMapper(值的规则)。

在底下有一段代码,虽然我们看不懂,但是可以猜。

HashMap::newnew 了一个 HashMap,然后将键、值的规则传递进去,传递给 uniqKeysMapAccumulator()

image-20240430162916018

跟进 uniqKeysMapAccumulator(),可以看见它里面调用了 putIfAbsent(),将数据添加到 Map集合 中。

image-20240430163233187

Function接口apply() 的对应关系如下

image-20240430164619339

注意点:如果我们要收集到Map集合当中,键不能重复,否则会报错。

image-20240430164745484

报错的原因就是因为它底层调用了 putIfAbsent(),这个方法里面会做一个判断:根据键先找值,如果值为 null,表示当前的键是不存在的,此时就将数据添加到 Map集合 中,但是在添加第二次的时候,这里的 v 就不是 null 了,直接返回 v

image-20240430164959096

ctrl + alt + ← 返回上一步,可以发现它在下面又做了一个判断:如果 u 不是 null,它就会产生异常

image-20240430165221007
Map<String, Integer> map = list.stream()
        .filter(s -> "男".equals(s.split("-")[1]))
        /*
         *   toMap : 参数一表示键的生成规则
         *           参数二表示值的生成规则
         *
         * 参数一:
         *       Function泛型一:表示流中每一个数据的类型
         *               泛型二:表示Map集合中键的数据类型
         *
         *        方法apply形参:依次表示流里面的每一个数据
         *               方法体:生成键的代码
         *               返回值:已经生成的键
         *
         *
         * 参数二:
         *        Function泛型一:表示流中每一个数据的类型
         *                泛型二:表示Map集合中值的数据类型
         *
         *       方法apply形参:依次表示流里面的每一个数据
         *               方法体:生成值的代码
         *               返回值:已经生成的值
         *
         * */
        .collect(Collectors.toMap(new Function<String, String>() {
                                      @Override
                                      public String apply(String s) {
                                          //张无忌-男-15
                                          return s.split("-")[0];
                                      }
                                  },
                new Function<String, Integer>() {
                    @Override
                    public Integer apply(String s) {
                        return Integer.parseInt(s.split("-")[2]);
                    }
                }));

改写为 lambda表达式

Map<String, Integer> map2 = list.stream()
    .filter(s -> "男".equals(s.split("-")[1]))
    .collect(Collectors.toMap(
        s -> s.split("-")[0],
        s -> Integer.parseInt(s.split("-")[2])));

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaStream的常用方法包括获取Stream、中间方法终结方法。 获取Stream方法有两种:集合获取Stream和数组获取Stream。集合获取Stream可以使用集合类的stream()方法,例如Map的keySet()方法可以获取键,values()方法可以获取值,entrySet()方法可以获取键值对。数组获取Stream可以使用Arrays类的stream()方法,将数组转换为Stream。 中间方法是对Stream进行操作的方法,一次操作完毕之后,还可以继续进行其他操作。常用的中间方法包括filter()、map()、flatMap()、distinct()、sorted()、limit()和skip()等。filter()方法用于过滤元素,map()方法用于对元素进行映射,flatMap()方法用于扁平化处理,distinct()方法用于去重,sorted()方法用于排序,limit()方法用于限制元素数量,skip()方法用于跳过元素。 终结方法Stream的最后一个操作,一个Stream只能有一个终结方法。常用的终结方法包括forEach()、collect()、count()、min()、max()、reduce()和toArray()等。forEach()方法用于遍历元素,collect()方法用于将元素收集到集合中,count()方法用于统计元素数量,min()方法用于获取最小值,max()方法用于获取最大值,reduce()方法用于对元素进行归约操作,toArray()方法用于将元素转换为数组。 综合应用Stream的常用方法可以实现对数据的筛选、转换、排序、统计等操作,提高代码的简洁性和可读性。 #### 引用[.reference_title] - *1* *2* *3* [Java 基础进阶篇(十二):Stream 常用方法总结](https://blog.csdn.net/weixin_43819566/article/details/130537949)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值