添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

开发中因为没有研究过这个东西 在使用toMap 的时候Key 重复导致 出错 很尴尬 所以要花时间把它扒光看看有多少货 以下个人理解 有错误地方可以评论指出 或者使用的场景也可评论 我会更新在的应用场景里面

算起来 自己也算经常使用 Stream 进行操作 但是只能说我使用了Stream 中的一小部分功能 想来大部分后端开发都比较了解 Stream所提供的方法 但是一直没有过于研究 Collectors 这个家伙 说起来这家伙算是很重要的一环 我们也会经常使用 但是更多都是 To 系列的东西

Collectors.toList()  Collectors.toSet()  Collectors.toMap()

这些肯定是冰山一角 就抽时间整理一下 Collectors 方法文档 留作查看

ToXXX 系列/收集、转换

这个系列基本都是收集器 将数据收起来 三大类 就是 ListSetMap 这个我们会比较常用 每种类型大概的说一下

Collectors.toList()

官方解释:返回将输入元素累积到新列表中的收集器。不保证返回的 List 的类型、可变性、可序列化性或线程安全性;如果需要对返回的列表进行更多控制,请使用 toCollection(Supplier)。

意思大家都能理解 就是将数据放到一个新的容器里面 会生成一个新的List 不能保证返回的 List 的类型、可变性、可序列化性或线程安全性

一般会自动解析 你原始List的泛型 但是不保证一定会有

Collectors.toSet()

官方解释: 返回一个收集器,它将输入元素累积到一个新的 Set 中。不保证返回的 Set 的类型、可变性、可序列化性或线程安全性;如果需要对返回的 Set 进行更多控制,请使用 toCollection(Supplier)。这是一个无序收集器。

就是一个去重的集合 下图中 数字 8 被去重

Collectors.toMap()

这是比较有意思的 也是我比较常用的 我就细一点记录

toMap(Key,Value)
  • 最简单的一种用法 我们可以选取对象中的 一个值作为Key 可以选择整个对象作为Value。也可以是某一个值 我个人比较常用的一个模式 但是熟悉的人应该都能看出来 下面这段代码 是错误的
  • List<EmailPushRecord> list = new ArrayList<>();
    EmailPushRecord emailPushRecord = new EmailPushRecord().builder()
            .sendTime("2022-09-13")
            .isSendSuccess(1)
            .messageNumber(123)
            .build();
    list.add(emailPushRecord);
    EmailPushRecord pushRecord = new EmailPushRecord();
    BeanUtils.copyProperties(emailPushRecord, pushRecord);
    pushRecord.setIsSendSuccess(2);
    list.add(pushRecord);
    Map<Integer, EmailPushRecord> map = list.stream().collect(Collectors.toMap(EmailPushRecord::getMessageNumber, Function.identity()));
    

    正常结果 是这个样子 但是上述代码中 我的两个对象中的messageNumber是重复的 所以就会出现错误

    Duplicate key 也就是说 最简单的模式 这个的Key 是不能重复的 但是我们有时候不能保证我们的Key 不会重复 我们就要使用下面另一种方法

    toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper)
    
    toMap(Key,Value,指定策略)
  • 这种方法可以解决Key重复问题 我们可以指定 当Key出现冲突的时候 使用 V2 覆盖 V1的值 也可以保留 V1的值 抛弃V2的值 也就是key相同 我们可以选择覆盖或者保留
  • 代码如下:

     List<EmailPushRecord> list = new ArrayList<>();
     EmailPushRecord emailPushRecord = new EmailPushRecord().builder()
             .sendTime("2022-09-13")
             .isSendSuccess(1)
             .messageNumber(123)
             .build();
     list.add(emailPushRecord);
     EmailPushRecord pushRecord = new EmailPushRecord();
     BeanUtils.copyProperties(emailPushRecord, pushRecord);
     pushRecord.setIsSendSuccess(2);
     list.add(pushRecord);
     Map<Integer, EmailPushRecord> cover = list.stream()
             .collect(Collectors.toMap(i -> i.getMessageNumber(),
                     Function.identity(), (v1, v2) -> v2));
     // 结果
     // {Integer@1292} 123 -> {EmailPushRecord@1178} "EmailPushRecord(messageNumber=123, isSendSuccess=2, sendTime=2022-09-13)"
     // 或者
     Map<Integer, EmailPushRecord> retain = list.stream()
             .collect(Collectors.toMap(i -> i.getMessageNumber(),
                     Function.identity(), (v1, v2) -> v1));
     // 结果
     //{Integer@1292} 123 -> {EmailPushRecord@1177} "EmailPushRecord(messageNumber=123, isSendSuccess=1, sendTime=2022-09-13)"
    

    上述代码可以看出来 结果是不同的 原因就是

    这个就解决了 我们使用的时候出现Key 重复报错的问题 可以指定 覆盖策略

    toMap(Key,Value,指定策略,指定Map 类型)
    LinkedHashMap<Integer, EmailPushRecord> linkedHashMap = list.stream().
            collect(Collectors.toMap(EmailPushRecord::getMessageNumber,
                    Function.identity(),
                    ((first, second) -> first),
                    LinkedHashMap::new));
    

    这个就是一眼懂了 根据需要去使用 不同的特性

  • 一个参数 返回map 可以指定key 和Value 但是key冲突会报错
  • 两个参数 返回map 可以设置Key覆盖策略 Key 重复不会报错
  • 三个参数 返回Map 有上面两个的属性 还能执行返回Map的类型前面两个 都是默认的HashMap
  • Collectors.toCollection()

    官方解释: 返回一个收集器,它将输入元素按照遇到的顺序累积到一个新的集合中。集合由提供的工厂创建。

    说实话 没看懂什么意思 这种官方解释翻译过来 就不太好理解了 个人感觉

  • T-输入元素的类型
  • C-结果集合的类型
  • 供应商:结果供应商
  • collectionFactory-返回适当类型的新的空Collection的供应商
  • 可以在 内部去指定结合 ArrayListTreeSetLinkedList

    一般这个都是做一些深层次的数据处理 比如去重

    Collectors.toConcurrentMap

    这基本都和上面的 toMap 相同 但是这个是无序的 并发的Map 在并发的环境下使用

    toMap -> 是一个非并发收集器

    toConcurrentMap -> 是一个并发收集器(这可以从它们的特性上看出来).

    不同的是,toMap 会创建多个中间结果,然后会合并在一起(这样的 Collector 的 Supplier 会被调用多次),而 toConcurrentMap 会创建一个单个结果,每个线程都会向它抛出结果(这样的收集器的供应商只会被调用一次)

    toMap 将通过合并多个中间结果(多次调用该收集器的供应商以及组合器)以遇到顺序将值插入结果地图中**

    toConcurrentMap 将通过将所有元素扔到一个公共结果容器(在本例中为 ConcurrentHashMap)以任何顺序(未定义)收集元素.供应商只被调用一次,累加器被多次调用,而组合器从不被调用.

    这个不做过多讲解 就是相对于toMap 是线程安全的 相当于 HashMapConcurrentHashMap 区别的思想

    GroupingByXXX() 系列/分组

    Collectors.groupingBy(EmailPushRecord::getMessageNumber)

    这个就是通过你的标示给你进行分组 还很很有用的开发中经常使用 可以看到下图生成的结果 一个Key 指向一个List

    Map<Integer, List<EmailPushRecord>> collect = list.stream().collect(Collectors.groupingBy(EmailPushRecord::getMessageNumber));
    

    Collectors.groupingBy(字段,归约条件)

    groupingBy(Function<? super T, ? extends K> classifier,
                                         Collector<? super T, A, D> downstream)
    

    这个也比较有意思 我们先看一下写法

    Map<Integer, List<String>> setMap = list.stream().collect(Collectors.groupingBy(EmailPushRecord::getMessageNumber, Collectors.mapping(EmailPushRecord::getSendTime, toList())));
    

    上述 和 类型一相比 有一个新的东西 Collectors.mapping 这个东西 我是理解为相当于 Sql中的Select 平常喜欢用 Mybatis-Plus 的人应该知道 MP有一个.select() 的函数用来查询我们需要的字段 Collectors.mapping也有点相同思想 我们可以对分组的结果集进一步处理 我理解的主要有两点

  • 用于返回想要的字段 过滤掉冗余的字段
  • 用于指定储存容器 如 toSet()、toList()
  • 官方例子:

    例如,计算每个城市的人的姓氏集合:
    Map<City, Set<String>> namesByCity = people.stream().collect(groupingBy(Person::getCity,
    mapping(Person::getLastName, toSet ())));
    

    Collectors.groupingBy(字段,map类型,归约条件)

    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,---> Map工厂
                                  Collector<? super T, A, D> downstream)
    

    这个其实就是指定 Map 的类型 默认是HashMap 我们如果需要返回有序的 可以指定为TreeMap

    Map<Integer, Set<String>> setMap = list.stream()
    .collect(Collectors.groupingBy(EmailPushRecord::getMessageNumber
    ,TreeMap::new, Collectors.mapping(EmailPushRecord::getSendTime, toSet())));
    

    注意看setMap 的类型是 TreeMap 根据不同需求可以使用不同的Map

    这里一定要是 Map下面的 派生类 TreeMap、HashMap等等

    官方例子:

    例如,要计算每个城市的人的姓氏集合,城市名称是排序的:
    {Map<City, Set<String>> namesByCity = people.stream()
    .collect (groupingBy(Person::getCity
    , TreeMap::new, mapping(Person::getLastName, toSet())));
    
    Collectors.groupingByConcurrent  用法一样 适合在并发下使用 不再进行记录
    
  • 类型一:简单的分组 默认Map 为hashMap  数据结构key->List
  • 类型二:默认Map为HashMap 可以指定返回的字段 和 集合类型
  • 类型三:在类型二的基础上可以更改Map的类型
  • Collectors.joining()系列 组装/转换

    这个东西的常用在集合数据转换成 String 类型的时候 有点像 String.join 主要提供了三种方式

    Collectors.joining() 基础款

    List<String> jo = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        jo.add(String.valueOf(i));
    String collect = jo.stream().collect(Collectors.joining());
    String join = String.join("", jo);
    // 结果0123456789
    System.out.println(join);
    //结果 0123456789
    System.out.println(collect);
    

    基本上没有什么区别 除了 String.Join 需要多加一个引号 但是仅仅处理 这种简单的数据数据类型 感觉上 有点鸡肋 但是 Stream 主要是集合杀手 所以 Collectors.joining()可以支持对象类型的数据

    String collect = list.stream().map(EmailPushRecord::getSendTime)
                                        .collect(Collectors.joining());
    //结果 2022-09-132022-09-13
    

    Collectors.joining("分割符") 一层加强

    joining(CharSequence delimiter)

    String collect = list.stream().map(EmailPushRecord::getSendTime)
                                        .collect(Collectors.joining("_"));
    //结果 2022-09-13_2022-09-13
    

    Collectors.joining("分割符",前缀,后缀) 二层加强

    joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

    String collect = list.stream().map(EmailPushRecord::getSendTime)
                                        .collect(Collectors.joining("_", "@", "#"));
    //结果 @2022-09-13_2022-09-13#
    
  • 类型一 不增加分割符 将数据转成一个String 123
  • 类型二 增加分割符 将数据转化成一个通过分割符分割的String 1,2,3
  • 类型三 增加前后缀 将数据转化成一个通过分割符分割的String 且有前后缀 @1,2,3# 可以结合Stream 操作object中的单个字段
  • Collectors.counting() 返回收集器个数

    这个结合 Stream 自带的方法 比如 去重、过滤 才有点意思

    Long collect = list.stream()
    .filter(item -> item.getIsSendSuccess() > 1).collect(Collectors.counting());
    //结果 1
    System.out.println(collect);
    

    这个就看 个人需求了。如果希望拿到处理后的数据的个数 可以尝试使用这个

    maxBy minBy 最大值最小值

    maxBy

    EmailPushRecord record = list.stream()
    .collect(Collectors
    .maxBy(Comparator.comparingInt(EmailPushRecord::getIsSendSuccess))).get();
    Integer integer = list.stream()
        .map(EmailPushRecord::getIsSendSuccess).max(Integer::compareTo).get();
    

    两种写法没有什么区别 可以使用 Stream().max() 也可以使用 另一种 目前没发现有什么特别之处 不再详细说明 后续研究特别之处后再做更新 minBy 取最小值 不再讲解

    Collectrs.averagingXXX 平均值

    List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
    Double aDouble = doubles.stream().collect(Collectors.averagingDouble(s -> s));
    //结果 3.3
    System.out.println(aDouble);
    

    这种有几种类型 averagingDouble、 averagingLong、 averagingInt 都是明面意思 要注意的是 数据类型 强转的时候精度丢失的问题

    Collectors.summingXXX 求和

    List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
    Double aDouble = doubles.stream().collect(Collectors.summingDouble(s -> s));
    doubles.stream().collect(Collectors.toList());
    // 结果 16.5
     System.out.println(aDouble);
    

    这个就是求和 需要注意使用的类型 防止出现精度问题 Stream 自带的也有 看个人习惯使用

    Double aDouble = doubles.stream().mapToDouble(s -> s).sum();
    

    Collectors.reducing() 数据运算

    reducing(BinaryOperator op)一个参数

    List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer integer = testData.stream().collect(Collectors.reducing((prev, cur) -> {
        System.out.println("prev==》" + prev + "cur==》" + cur);
        return prev + cur;
    })).get();
    System.out.print(integer); // 45
    
    List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer integer = testData.stream().collect(Collectors.reducing((prev, cur) -> {
        System.out.println("prev==》" + prev + "cur==》" + cur);
        return prev / cur;
    })).get();
    //prev==》1cur==》2
    //prev==》0cur==》3
    //prev==》0cur==》4
    //prev==》0cur==》5
    //prev==》0cur==》6
    //prev==》0cur==》7
    //prev==》0cur==》8
    //prev==》0cur==》9
    System.out.print(integer); 
    

    可以对数据进行加减乘除操作

    reducing(T identity, BinaryOperator op)二个参数

  • T identity 返回类型T初始值
  • BinaryOperator op 归集操作函数 输入参数T返回T
  • 最终结果 加上 前面的identity 这里是负数所以相当于减

    List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer integer = testData.stream().collect(Collectors.reducing(-20, (prev, cur) -> {
        System.out.println("prev==》" + prev + "cur==》" + cur);
        return prev + cur;
    // 25
    System.out.print(integer);
    

    reducing(U identity,Function<? super T, ? extends U> mapper,BinaryOperator op) 三个参数

  • U identity 最终返回类型U初始值
  • Function<? super T, ? extends U> mapper 将输入参数T转换成返回类型U的函数\
  • BinaryOperator op 归集操作函数 输入参数U返回U
  • List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    String collect = testData.stream().collect(Collectors.reducing("测试转换", in -> in + "", (perv, cur) -> perv + "," + cur));
    // 测试转换,1,2,3,4,5,6,7,8,9
    System.out.print(collect);
    

    这个可以用来拼接 个人也没用过 具体使用场景还需要琢磨一下

    Collectrs.partitioningBy() 分类规约

    partitioningBy(Predicate<? super T> predicate) 一个参数

    这个感觉是有点像filter 的增强版 可以通过条件返回 符合条件的和不符合条件的

    partitioningBy(Predicate<? super T> predicate,Collector<? super T, A, D> downstream) 两个参数

    可以使用Collectors的一些属性 指定返回的数据和接收集合等等

    List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Map<Boolean, Long> collect = testData.stream().collect(Collectors.partitioningBy(num -> (num > 3), Collectors.counting()));
    Map<Boolean, Set<Integer>> setMap = testData.stream().collect(Collectors.partitioningBy(num -> (num > 3), Collectors.toSet()));
    System.out.print(collect);
    
  • 一个参数 可以做的是通过你设置的条件 返回数据集 符合条件key->true 不符合条件 Key->false
  • 两个参数 可以使用Collectors的一些属性 指定返回的数据和接收集合等等
  • 搞完了 也算更加了解了 对于其中的一些方法使用也算是拿捏了 这个还是比较经常用到的 扒光后豁然开朗 这个圣诞老人胡子真白 不好意思评论的话 点个赞吧 🥰🥰🥰

    全栈开发 @ 🏆糊涂码专家 244.5k
    粉丝