深入讲解JavaStream终结操作:归约、分组与分区

2023年 7月 12日 71.9k 0

深入讲解JavaStream终结操作:归约、分组与分区

思维导图镇楼。

上一篇中给大家讲了Stream的前半部分知识——包括对Stream的整体概览及Stream的创建和Stream的转换流操作,并对Stream一些内部优化点做了简明的说明。

虽迟但到,今天就来继续给大家更Stream第二部分知识——终结操作,由于这部分的API内容繁多且复杂,所以我单开一篇给大家细细讲讲。

正式开始之前,我们先来说说聚合方法本身的特性(接下来我将用聚合方法代指终结操作中的方法):

  • 聚合方法代表着整个流计算的最终结果,所以它的返回值都不是Stream。
  • 聚合方法返回值可能为空,比如filter没有匹配到的情况,JDK8中用Optional来规避NPE。
  • 聚合方法都会调用evaluate方法,这是一个内部方法,看源码的过程中可以用它来判定一个方法是不是聚合方法。
  • ok,知晓了聚合方法的特性,我为了便于理解,又将聚合方法分为几大类:

    深入讲解JavaStream终结操作:归约、分组与分区

    其中简单聚合方法我会简单讲解,其它则会着重讲解,尤其是收集器,它能做的实在太多了。。。

    Stream的聚合方法是我们在使用Stream中的必用操作,认真学习本篇,不说马上就能对Stream得心应手,起码也可以行云流水吧?

    深入讲解JavaStream终结操作:归约、分组与分区

    1. 简单聚合方法

    第一节嘛,先来点简单的。

    Stream的聚合方法比上一篇讲过的无状态和有状态方法都要多,但是其中也有一些是喵一眼就能学会的,第一节我们先来说说这部分方法:

    • count():返回Stream中元素的size大小。
    • forEach():通过内部循环Stream中的所有元素,对每一个元素进行消费,此方法没有返回值。
    • forEachOrder():和上面方法的效果一样,但是这个可以保持消费顺序,哪怕是在多线程环境下。
    • anyMatch(Predicate predicate):这是一个短路操作,通过传入断言参数判断是否有元素能够匹配上断言。
    • allMatch(Predicate predicate):这是一个短路操作,通过传入断言参数返回是否所有元素都能匹配上断言。
    • noneMatch(Predicate predicate):这是一个短路操作,通过传入断言参数判断是否所有元素都无法匹配上断言,如果是则返回true,反之则false。
    • findFirst():这是一个短路操作,返回Stream中的第一个元素,Stream可能为空所以返回值用Optional处理。
    • findAny():这是一个短路操作,返回Stream中的任意一个元素,串型流中一般是第一个元素,Stream可能为空所以返回值用Optional处理。

    虽然以上都比较简单,但是这里面有五个涉及到短路操作的方法我还是想提两嘴:

    首先是findFirst()findAny()这两个方法, 由于它们只需要拿到一个元素就能方法就能结束,所以短路效果很好理解。

    接着是anyMatch方法,它只需要匹配到一个元素方法也能结束,所以它的短路效果也很好理解。

    最后是allMatch方法和noneMatch,乍一看这两个方法都是需要遍历整个流中的所有元素的,其实不然,比如allMatch只要有一个元素不匹配断言它就可以返回false了,noneMatch只要有一个元素匹配上断言它也可以返回false了,所以它们都是具有短路效果的方法。

    2. 归约

    2.1 reduce:反复求值

    第二节我们来说说归约,由于这个词过于抽象,我不得不找了一句通俗易懂的解释来翻译这句话,下面是归约的定义:

    将一个Stream中的所有元素反复结合起来,得到一个结果,这样的操作被称为归约。

    注:在函数式编程中,这叫做折叠( fold )。

    举个很简单的例子,我有1、2、3三个元素,我把它们俩俩相加,最后得出6这个数字,这个过程就是归约。

    再比如,我有1、2、3三个元素,我把它们俩俩比较,最后挑出最大的数字3或者挑出最小的数字1,这个过程也是归约。

    下面我举一个求和的例子来演示归约,归约使用reduce方法:

            Optional reduce = List.of(1, 2, 3).stream()
                    .reduce((i1, i2) -> i1 + i2);

    首先你可能注意到了,我在上文的小例子中一直在用俩俩这个词,这代表归约是俩俩的元素进行处理然后得到一个最终值,所以reduce的方法的参数是一个二元表达式,它将两个参数进行任意处理,最后得到一个结果,其中它的参数和结果必须是同一类型。

    比如代码中的,i1和i2就是二元表达式的两个参数,它们分别代表元素中的第一个元素和第二个元素,当第一次相加完成后,所得的结果会赋值到i1身上,i2则会继续代表下一个元素,直至元素耗尽,得到最终结果。

    如果你觉得这么写不够优雅,也可以使用Integer中的默认方法:

            Optional reduce = List.of(1, 2, 3).stream()
                    .reduce(Integer::sum);

    这也是一个以方法引用代表lambda表达式的例子。

    你可能还注意到了,它们的返回值是Optional的,这是预防Stream没有元素的情况。

    你也可以想办法去掉这种情况,那就是让元素中至少要有一个值,这里reduce提供一个重载方法给我们:

            Integer reduce = List.of(1, 2, 3).stream()
                    .reduce(0, (i1, i2) -> i1 + i2);

    如上例,在二元表达式前面多加了一个参数,这个参数被称为初始值,这样哪怕你的Stream没有元素它最终也会返回一个0,这样就不需要Optional了。

    在实际方法运行中,初始值会在第一次执行中占据i1的位置,i2则代表Stream中的第一个元素,然后所得的和再次占据i1的位置,i2代表下一个元素。

    不过使用初始值不是没有成本的,它应该符合一个原则:accumulator.apply(identity, i1) == i1,也就是说在第一次执行的时候,它的返回结果都应该是你Stream中的第一个元素。

    比如我上面的例子是一个相加操作,则第一次相加时就是0 + 1 = 1,符合上面的原则,作此原则是为了保证并行流情况下能够得到正确的结果。

    如果你的初始值是1,则在并发情况下每个线程的初始化都是1,那么你的最终和就会比你预想的结果要大。

    2.2 max:利用归约求最大

    max方法也是一个归约方法,它是直接调用了reduce方法。

    先来看一个示例:

            Optional max = List.of(1, 2, 3).stream()
                    .max((a, b) -> {
                        if (a > b) {
                            return 1;
                        } else {
                            return -1; 
                        }
                    });

    没错,这就是max方法用法,这让我觉得我不是在使用函数式接口,当然你也可以使用Integer的方法进行简化:

            Optional max = List.of(1, 2, 3).stream()
                    .max(Integer::compare);

    哪怕如此,这个方法依旧让我感觉到很繁琐,我虽然可以理解在max方法里面传参数是为了让我们自己自定义排序规则,但我不理解为什么没有一个默认按照自然排序进行排序的方法,而是非要让我传参数。

    直到后来我想到了基础类型Stream,果然,它们里面是可以无需传参直接拿到最大值:

            OptionalLong max = LongStream.of(1, 2, 3).max();

    果然,我能想到的,类库设计者都想到了~

    注 :OptionalLong是Optional对基础类型long的封装。

    2.3 min:利用归约求最小

    min还是直接看例子吧:

            Optional max = List.of(1, 2, 3).stream()
                    .min(Integer::compare);

    它和max区别就是底层把 > 换成了 

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论