【问题标题】:Is it possible to collect a stream to two different collections using one line?是否可以使用一行将流收集到两个不同的集合?
【发布时间】:2016-03-12 02:19:09
【问题描述】:

我有以下代码:(为简洁起见)

public void search(Predicate<String> predicate, Elements elements)
{
    List<SearchResult> searchResults = elements.stream()
        .filter(element -> predicate.test(element.ownText()))
        .map(element -> new SearchResult(element.ownText(), element.baseUri(),element.tagName()))
        .collect(Collectors.toList());
}

但是现在,我想要另一个列表,其中包含所有没有映射的过滤元素。 是否可以使用流来执行此操作,还是应该为此更改为 foreach 循环?

【问题讨论】:

  • 您可以使用自定义收集器来完成。但另一种解决方案是拥有 2 个 Stream 管道。

标签: java java-stream


【解决方案1】:

您可以收集过滤后的元素,然后使用此列表将元素映射到搜索结果:

public void search(Predicate<String> predicate, Elements elements) {
    List<Element> filteredElements = 
        elements.stream()
                .filter(element -> predicate.test(element.ownText()))
                .collect(Collectors.toList());

    List<SearchResult> searchResults = 
        filteredElements.stream()
                        .map(element -> new SearchResult(element.ownText(),element.baseUri(),element.tagName()))
                        .collect(Collectors.toList());
}

这不会比其他答案的解决方案花费更多时间,但没有副作用,线程安全且易于阅读和理解。

【讨论】:

    【解决方案2】:

    一个简单的方法是

    List<Element> filteredElements = new ArrayList<>();
    List<SearchResult> searchResults = elements.stream()
          .filter(element -> predicate.test(element.ownText()))
          .peek(filteredElements::add)
          .map(element -> 
             new SearchResult(element.ownText(),element.baseUri(),element.tagName()))
          .collect(Collectors.toList());
    

    【讨论】:

    • 漂亮优雅!谢谢。
    • peek 的这种使用给我一种非常糟糕的感觉,尽管我无法说明它有什么具体的问题。感觉就像是在滥用偷看。
    • @DavidConrad 因为来自peek Javadoc:“此方法的存在主要是为了支持调试,您希望在元素流过管道中的某个点时查看它们 ”。
    • @DavidConrad 你的感觉可能会刺痛,因为peek(filteredElements::add) 引入了副作用(向filteredElements 添加元素)。此外,当elements.stream() 返回并行流时,filteredElements 可能不包含所有过滤的元素,因为 ArrayList 不是线程安全的。
    • @DavidConrad 我添加了一个解决方案的答案,该解决方案既线程安全又没有副作用。
    【解决方案3】:

    另一种方法是定义Supplier&lt;Stream&gt;

    public void search(Predicate<String> predicate, Elements elements)
    {
        Supplier<Stream<Element>> supplier = () -> elements.stream()
               .filter(element -> predicate.test(element.ownText()));
        List<SearchResult> searchResults = supplier.get()
               .map(element -> new SearchResult(element.ownText(),element.baseUri(),element.tagName()))
               .collect(Collectors.toList());
        List<Element> elementList = supplier.get().collect(Collectors.toList());
    }
    

    请注意,使用这种方法实际上执行了两次过滤。

    另一种(虽然在这种情况下不是很漂亮)解决方案是使用来自this answerpairing 收集器:

    Collector<Element, ?, List<SearchResult>> c1 = 
        Collectors.mapping(element -> new SearchResult(element.ownText(),element.baseUri(),element.tagName()),
            Collectors.toList());
    Collector<Element, ?, List<Element>> c2 = Collectors.toList();
    Collector<Element, ?, Pair<List<SearchResult>, List<Element>>> pairCollector =
        pairing(c1, c2, Pair::new); // Assumes some Pair class existing
    
    Pair<List<SearchResult>, List<Element>> result = elements.stream()
               .filter(element -> predicate.test(element.ownText()))
               .collect(pairing);
    

    这些解决方案是通用的:它们允许对单个数据源执行两种不同的操作。但是在您的具体示例中,创建第一个过滤的非映射数据列表,然后在该列表上创建第二个流更容易:

    List<Element> elementList = elements.stream()
               .filter(element -> predicate.test(element.ownText()))
               .collect(Collectors.toList());
    List<SearchResult> searchResults = elementList.stream()
               .map(element -> new SearchResult(element.ownText(),element.baseUri(),element.tagName()))
               .collect(Collectors.toList());
    

    【讨论】:

    • 我认为最后一种方法在这种情况下是最好的。但是这里实际上有 3 个非常不同的答案,也许值得将它们分开?
    【解决方案4】:

    您不能使用该流两次,因为在使用 collect 时它已关闭。 以下代码将抛出“java.lang.IllegalStateException:流已被操作或关闭”。因此,您需要创建两次流或使用 for-each 循环。

    import java.util.Collections;
    import java.util.List;
    import java.util.function.Predicate;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    class Test
    {
        static class SearchResult
        {
            SearchResult(Object o,Object p,Object q) {}
        }
    
        static class Element
        {
            public String ownText() {return "text";}
            public String tagName() {return "tag";}
            public String baseUri() {return "base";}
        }
    
        public static void search(Predicate<String> predicate, List<Element> elements)
        {
            Stream<Element> stream = elements.stream().filter(element -> predicate.test(element.ownText()));
            List<Element> filtered = stream.collect(Collectors.toList());
            // next line will throw the IllegalStateException as above line has already consumed the stream
            List<SearchResult> results =    stream.map(element -> 
             new SearchResult(element.ownText(),element.baseUri(),element.tagName()))
                    .collect(Collectors.toList());
        }
    
        public static void main(String[] args)
        {       
            search(s->true,Collections.singletonList(new Element()));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-24
      • 2019-11-02
      • 1970-01-01
      • 2015-02-21
      • 2018-12-11
      • 2020-11-04
      相关资源
      最近更新 更多