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());