【问题标题】:Replace forEach with Stream将 forEach 替换为 Stream
【发布时间】:2019-12-12 19:20:55
【问题描述】:

我有以下代码

TreeMap<Integer, List<String>> myMap = new TreeMap<>();
List<Integer> myList = getDataForTest(true);
List<String> wordList = getWordList(true);
List<Integer> intList = Collections.emptyList();
intList = myObj.stream().filter(e->e.getType.equalsIgnoreCase("TEST")).map(e->e.getIntPos()).collect(Collectors.toList())
AtomicInteger pos = new AtomicInteger(0);
myList.stream()
            .forEach((entry) ->{
                                if(CollectionUtils.isEmpty(intList) || intList.contains(pos.get()))
                                {
                                    myMap.computeIfAbsent(myList.get(pos.get()),k-> new ArrayList<>()).add(wordList.get(pos.get()));
                                }
                                pos.incrementAndGet();
                                    });
return myMap;

上面的代码显然会抛出一个编译错误,因为我使用的 intList 在 lambda 中不是最终的。有没有办法让这个 lambda 被流替换。

【问题讨论】:

  • 展示你的代码当然很重要。但同样或更重要的是显示带有一些值的起始数据结构是什么样的,以及填充了这些相同值的最终所需数据结构。请提供一个minimal reproducible example 这样做。

标签: java java-stream


【解决方案1】:

正如this answer 中所说,空列表的初始分配已过时,当您删除它时,只剩下一个分配,允许从 lambda 表达式引用它。但是,当您对集合所做的只是检查它是否为空或调用contains 时,您最好使用Set,以便更快地查找:

List<Integer> myList = getDataForTest(true);
List<String> wordList = getWordList(true);

Set<Integer> intSet = myObj.stream()
    .filter(e -> e.getType().equalsIgnoreCase("TEST")).map(e -> e.getIntPos())
    .collect(Collectors.toSet());

IntStream indices = IntStream.range(0, myList.size());
if(!intSet.isEmpty()) indices = indices.filter(intSet::contains);
return indices.boxed().collect(Collectors.groupingBy(myList::get,
    TreeMap::new, Collectors.mapping(wordList::get, Collectors.toList())));

但是还有一个选择。我们可以首先迭代集合的索引,而不是通过测试它们在集合中的存在来过滤所有列表索引。我们只需要将它们排序和区分,以与通过过滤器的range 索引相同的方式遇到。对于这种操作,一组原始的int 值更适合:

List<Integer> myList = getDataForTest(true);
List<String> wordList = getWordList(true);

int[] ints = myObj.stream()
    .filter(e -> e.getType().equalsIgnoreCase("TEST")).mapToInt(e -> e.getIntPos())
    .sorted().distinct()
    .toArray();

return (ints.length == 0? IntStream.range(0, myList.size()): Arrays.stream(ints))
    .boxed()
    .collect(Collectors.groupingBy(myList::get, TreeMap::new,
        Collectors.mapping(wordList::get, Collectors.toList())));

如果不将“无索引”作为“无过滤”进行特殊处理,我们甚至可以在单个流操作中完成。

【讨论】:

    【解决方案2】:

    假设这是您的实际代码,为什么不直接将其定为最终代码?替换

    List<Integer> intList = Collections.emptyList();
    intList = myObj.stream().filter(e->e.getType.equalsIgnoreCase("TEST")).map(e->e.getIntPos()).collect(Collectors.toList())
    

    final List<Integer> intList = myObj.stream().filter(e -> e.getType.equalsIgnoreCase("TEST")).map(e -> e.getIntPos()).collect(Collectors.toList())
    

    除此之外,我认为不可能用流替换您的 forEach,因为您在遍历列表时实际上并没有对列表的 entrys 做任何事情。实际上,我不太确定您实际上要完成什么。但是为什么不用一个简单的 for 循环来代替呢?

    for (Integer myVal : myList){
        if (CollectionUtils.isEmpty(intList) || intList.contains(pos.get())){
            myMap.computeIfAbsent(myList.get(pos.get()), k -> new ArrayList<>()).add(wordList.get(pos.get()));
        }
        pos.incrementAndGet();
    }
    

    【讨论】:

    • OP 的代码不使用entry 参数,而是使用myList.get(pos.get()),它的计算结果相同。请注意,使用循环时,您可以使用普通的int 变量而不是AtomicInteger。此外,这里不需要 3rd 方库类CollectionUtilsCollectionUtils.isEmpty(intList) 和直截了当的intList.isEmpty() 之间的唯一区别是对null 的处理,但intList 绝不是null此时。
    【解决方案3】:

    我看了一会儿,根据我自己的列表想出了一个解决方案 测试数据。

    鉴于以下情况:

          List<Integer> myList = List.of(1, 2, 3, 4, 2);
          List<String> wordList = List.of("A", "B", "C", "D", "E");
          List<Integer> intList = List.of(1, 4, 2);
    

    这个流解决方案

          AtomicInteger pos = new AtomicInteger(-1);
          System.out.println(myMap);
          Map<Integer, List<String>> myMap2 = myList.stream().filter(
                d ->!intList.isEmpty() &&
                      intList.contains(pos.incrementAndGet())).collect(
                      Collectors.groupingBy(d -> d,
                            TreeMap::new,
                            Collectors.mapping(d -> wordList.get(pos.get()),
                                  Collectors.toList())));
          System.out.println(myMap2);
    

    你的解决方案会产生

    {2=[B, E], 3=[C]}

    我确信可以对其进行调整以提供您想要的。

    但是,在 myList 上使用常规的 forEach 循环并没有错。它还允许您摆脱atomic integer,因为没有effective final 限制。

          int i = 0;
          for (int entry : myList) {
             if (!intList.isEmpty() && intList.contains(i)) {
                System.out.println("entry = " + entry);
                myMap.computeIfAbsent(myList.get(i), k -> new ArrayList<>()).add(
                      wordList.get(i));
             }
             i++;
          }
    

    【讨论】:

    • 卡布隆!谢谢。我相信我修复了它(它适用于空列表,也像以前一样)。但我仍然不知道OP到底想要什么。我就等着看他们有没有回应。
    猜你喜欢
    • 2017-01-02
    • 2019-11-08
    • 1970-01-01
    • 2022-11-28
    • 1970-01-01
    • 2021-02-11
    • 2023-03-31
    • 2023-03-11
    相关资源
    最近更新 更多