归约、分组与分区,Java教学视频百度云

2.2 max:利用归约求最大

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

先来看一个示例:


 Optional<Integer> max = List.of(1, 2, 3).stream()

                .max((a, b) -> {

                    if (a > b) {

                        return 1;

                    } else {

                        return -1; 

                    }

                });

复制代码

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


 Optional<Integer> 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<Integer> max = List.of(1, 2, 3).stream()

                .min(Integer::compare);

复制代码

它和max区别就是底层把 > 换成了 <,过于简单,不再赘述。

3. 收集器


第三节我们来看看收集器,它的作用是对Stream中的元素进行收集而形成一个新的集合。

虽然我在本篇开头的时候已经给过一张思维导图了,但是由于收集器的API比较多所以我又画了一张,算是对开头那张的补充:

收集器的方法名是collect,它的方法定义如下:


 <R, A> R collect(Collector<? super T, A, R> collector);

复制代码

顾名思义,收集器是用来收集Stream的元素的,最后收集成什么我们可以自定义,但是我们一般不需要自己写,因为JDK内置了一个Collector的实现类——Collectors。

3.1 收集方法

通过Collectors我们可以利用它的内置方法很方便的进行数据收集:

比如你想把元素收集成集合,那么你可以使用toCollection或者toList方法,不过我们一般不使用toCollection,因为它需要传参数,没人喜欢传参数。

你也可以使用toUnmodifiableList,它和toList区别就是它返回的集合不可以改变元素,比如删除或者新增。

再比如你要把元素去重之后收集起来,那么你可以使用toSet或者toUnmodifiableSet。

接下来放一个比较简单的例子:


 // toList

        List.of(1, 2, 3).stream().collect(Collectors.toList());



        // toUnmodifiableList

        List.of(1, 2, 3).stream().collect(Collectors.toUnmodifiableList());



        // toSet

        List.of(1, 2, 3).stream().collect(Collectors.toSet());



        // toUnmodifiableSet

        List.of(1, 2, 3).stream().collect(Collectors.toUnmodifiableSet());

复制代码

以上这些方法都没有参数,拿来即用,toList底层也是经典的ArrayList,toSet 底层则是经典的HashSet。


也许有时候你也许想要一个收集成一个Map,比如通过将订单数据转成一个订单号对应一个订单,那么你可以使用toMap():


 List<Order> orders = List.of(new Order(), new Order());



        Map<String, Order> map = orders.stream()

                .collect(Collectors.toMap(Order::getOrderNo, order -> order));

复制代码

toMap() 具有两个参数:

  1. 第一个参数代表key,它表示你要设置一个Map的key,我这里指定的是元素中的orderNo。

  2. 第二个参数代表value,它表示你要设置一个Map的value,我这里直接把元素本身当作值,所以结果是一个Map<String, Order>。

你也可以将元素的属性当作值:


 List<Order> orders = List.of(new Order(), new Order());



        Map<String, List<Item>> map = orders.stream()

                .collect(Collectors.toMap(Order::getOrderNo, Order::getItemList));

复制代码

这样返回的就是一个订单号+商品列表的Map了。

toMap() 还有两个伴生方法:

  • toUnmodifiableMap():返回一个不可修改的Map。

  • toConcurrentMap():返回一个线程安全的Map。

这两个方法和toMap() 的参数一模一样,唯一不同的就是底层生成的Map特性不太一样,我们一般使用简简单单的toMap() 就够了,它的底层是我们最常用的HashMap() 实现。

toMap() 功能虽然强大也很常用,但是它却有一个致命缺点。

我们知道HahsMap遇到相同的key会进行覆盖操作,但是toMap() 方法生成Map时如果你指定的key出现了重复,那么它会直接抛出异常。

比如上面的订单例子中,我们假设两个订单的订单号一样,但是你又将订单号指定了为key,那么该方法会直接抛出一个IllegalStateException,因为它不允许元素中的key是相同的。

3.2 分组方法

如果你想对数据进行分类,但是你指定的key是可以重复的,那么你应该使用groupingBy 而不是toMap。

举个简单的例子,我想对一个订单集合以订单类型进行分组,那么可以这样:


 List<Order> orders = List.of(new Order(), new Order());



        Map<Integer, List<Order>> collect = orders.stream()

                .collect(Collectors.groupingBy(Order::getOrderType));

复制代码

直接指定用于分组的元素属性,它就会自动按照此属性进行分组,并将分组的结果收集为一个List。


 List<Order> orders = List.of(new Order(), new Order());



        Map<Integer, Set<Order>> collect = orders.stream()

                .collect(Collectors.groupingBy(Order::getOrderType, toSet()));

复制代码

groupingBy还提供了一个重载,让你可以自定义收集器类型,所以它的第二个参数是一个Collector收集器对象。

对于Collector类型,我们一般还是使用Collectors类,这里由于我们前面已经使用了Collectors,所以这里不必声明直接传入一个toSet()方法,代表我们将分组后的元素收集为Set。

groupingBy还有一个相似的方法叫做groupingByConcurrent(),这个方法可以在并行时提高分组效率,但是它是不保证顺序的,这里就不展开讲了。

3.3 分区方法

接下来我将介绍分组的另一种情况——分区,名字有点绕,但意思很简单:

将数据按照TRUE或者FALSE进行分组就叫做分区。

举个例子,我们将一个订单集合按照是否支付进行分组,这就是分区:


 List<Order> orders = List.of(new Order(), new Order());

        

        Map<Boolean, List<Order>> collect = orders.stream()

                .collect(Collectors.partitioningBy(Order::getIsPaid));        



复制代码

因为订单是否支付只具有两种状态:已支付和未支付,这种分组方式我们就叫做分区。

和groupingBy一样,它还具有一个重载方法,用来自定义收集器类型:


 List<Order> orders = List.of(new Order(), new Order());



        Map<Boolean, Set<Order>> collect = orders.stream()

                .collect(Collectors.partitioningBy(Order::getIsPaid, toSet()));

复制代码

3.4 经典复刻方法

终于来到最后一节了,请原谅我给这部分的方法起了一个这么土的名字,但是这些方法确实如我所说:经典复刻。

换言之,就是Collectors把Stream原先的方法又实现了一遍,包括:

  1. mapmapping

  2. filterfiltering

  3. flatMapflatMapping

  4. countcounting

  5. reducereducing

  6. maxmaxBy

  7. **min ** → minBy

这些方法的功能我就不一一列举了,之前的文章已经讲的很详尽了,唯一的不同是某些方法多了一个参数,这个参数就是我们在分组和分区里面讲过的收集参数,你可以指定收集到什么容器内。

我把它们抽出来主要想说的为什么要复刻这么多方法处理,这里我说说个人见解,不代表官方意见。

我觉得主要是为了功能的组合。

什么意思呢?比方说我又有一个需求:使用订单类型对订单进行分组,并找出每组有多少个订单。

订单分组我们已经讲过了,找到其每组有多少订单只要拿到对应list的size就行了,但是我们可以不这么麻烦,而是一步到位,在输出结果的时候键值对就是订单类型和订单数量:


 Map<Integer, Long> collect = orders.stream()

                .collect(Collectors.groupingBy(Order::getOrderType, counting()));

复制代码

就这样,就这么简单,就好了,这里等于说我们对分组后的数据又进行了一次计数操作。

上面的这个例子可能不对明显,当我们需要对最后收集之后的数据在进行操作时,一般我们需要重新将其转换成Stream然后操作,但是使用Collectors的这些方法就可以让你很方便的在Collectors中进行数据的处理。

再举个例子,还是通过订单类型对订单进行分组,但是呢,我们想要拿到每种类型订单金额最大的那个,那么我们就可以这样:

文末java面试题,进阶技术大纲,架构资料分享

我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档,有兴趣的朋友们可以点击这里即可免费领取

  • 第一个要分享给大家的就是算法和数据结构

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 第二个就是数据库的高频知识点与性能优化

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 第三个则是并发编程(72个知识点学习)

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

子,还是通过订单类型对订单进行分组,但是呢,我们想要拿到每种类型订单金额最大的那个,那么我们就可以这样:

文末java面试题,进阶技术大纲,架构资料分享

我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档,有兴趣的朋友们可以点击这里即可免费领取

  • 第一个要分享给大家的就是算法和数据结构

[外链图片转存中…(img-QTXZCdTW-1628441109908)]

  • 第二个就是数据库的高频知识点与性能优化

[外链图片转存中…(img-GzbSgPSR-1628441109910)]

  • 第三个则是并发编程(72个知识点学习)

[外链图片转存中…(img-gTYXBwjj-1628441109911)]

  • 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料

[外链图片转存中…(img-bpMQkc2x-1628441109912)]

还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值