Reputation: 34313
I have a couple of predicates that I all want to be satisfied.
The things that can satisfy those predicates are a handful of strings. An individual string doesn't have to satisfy all (or any) of those predicates, but after I've looked at the last string, all of the predicates have to be satisified.
My first take to represent this problem in Java was to use Stream's allMatch
and anyMatch
since I want all of the predicates to match any of the things to test:
Stream<String> thingsToTest = Stream.of("Hi", "predicates!", "oddball");
Predicate<String> startsWithH = string -> string.startsWith("H");
Predicate<String> endsWithBang = string -> string.endsWith("!");
Stream<Predicate<String>> predicates = Stream.of(startsWithH, endsWithBang);
// All of the strings have the chance to satisfy any predicate
boolean predicatesSatisfied = predicates.allMatch(pred -> thingsToTest.anyMatch(pred::test));
// I expect this to print "true"
System.out.println(predicatesSatisfied);
Sadly, this doesn't work but terminates with an IllegalStateException
, telling me that the stream has already been operated upon or closed
, which shouldn't come as a big surprise since for each predicate I give the strings a new chance to satisfy the predicate, using the string stream over and over.
And streams are not meant to be reused for good reasons.
So how do I avoid this exception? Is there a more elegant alternative to anyMatch
or allMatch
?
Upvotes: 1
Views: 1456
Reputation: 100199
When you need to use the Stream several times, the common solution is to create a Supplier<Stream>
instead:
Supplier<Stream<String>> thingsToTest = () -> Stream.of("Hi", "predicates!", "oddball");
....
boolean predicatesSatisfied = predicates.allMatch(
pred -> thingsToTest.get().anyMatch(pred::test));
Unlike @MatthiasBraun suggestion, using the Supplier
it's not always necessary to actually store all the stream elements in the verbatim collection. For example, such thing is possible:
Supplier<Stream<String>> thingsToTest =
() -> IntStream.range(0, 10000).mapToObj(String::valueOf);
You just have to care that supplier always returns the same stream elements.
If you already have a collection, then you can create a supplier as well:
List<String> list = Arrays.asList("Hi", "predicates!", "oddball");
Supplier<Stream<String>> thingsToTest = list::stream;
Upvotes: 1
Reputation: 34313
To get around the IllegalStateException
I use a List
of strings and call its stream()
method:
// Use List instead of Stream
List<String> thingsToTest = Arrays.asList("Hi", "predicates!", "oddball");
// Same old
Predicate<String> startsWithH = string -> string.startsWith("H");
Predicate<String> endsWithBang = string -> string.endsWith("!");
Stream<Predicate<String>> predicates = Stream.of(startsWithH, endsWithBang);
// Call stream() on the List
boolean predicatesSatisfied = predicates.allMatch(pred -> thingsToTest.stream().
anyMatch(pred::test));
Although this works fine, I'm not sure if it is the most elegant way to do this, so if you have a better idea, please go ahead and post your code or suggestion.
Upvotes: 3