问题
I have the following code: (simplified for bravity)
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());
}
But now, I want to have another list which would contain all filtered elements without the mapping. Is it possible to do that with a stream, or should I change to a foreach loop for that?
回答1:
You can collect the elements after filtering and then use this list to map the elements to search results:
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());
}
This won't take more time than the solutions of the other answers but doesn't have side effects, is thread safe as well as easy to read and understand.
回答2:
A simple way to do this would be
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());
回答3:
One alternative is to define a Supplier<Stream>
:
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());
}
Note that using this approach you actually perform the filtering twice.
An alternative (though not very beautiful in this case) solution is use pairing
collector from this answer:
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);
These solutions are generic: they allow to do two different operations with single data source. But in your concrete example it's easier to create first list of filtered non-mapped data, then create a second stream on that list:
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());
回答4:
You cannot use the stream twice, as it is closed when using collect. The following code will throw an "java.lang.IllegalStateException: stream has already been operated upon or closed". So you need to create the stream twice or use a for-each loop.
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()));
}
}
来源:https://stackoverflow.com/questions/34134263/is-it-possible-to-collect-a-stream-to-two-different-collections-using-one-line